From 8156b60590a9950017d9f40f71d75549cbede6c5 Mon Sep 17 00:00:00 2001 From: Arpit Kamboj Date: Tue, 10 Apr 2018 14:56:08 +0530 Subject: [PATCH] Redesigned Groovy Setup - Ported from TS --- config/groovy/common.groovy | 354 +++++++++++++++++++++++++ config/groovy/module.groovy | 88 +++++++ config/groovy/util.groovy | 216 +++++++++++++++ groovyw | 2 +- groovyw.bat | 2 +- module.groovy | 512 ------------------------------------ 6 files changed, 660 insertions(+), 514 deletions(-) create mode 100644 config/groovy/common.groovy create mode 100644 config/groovy/module.groovy create mode 100644 config/groovy/util.groovy delete mode 100644 module.groovy diff --git a/config/groovy/common.groovy b/config/groovy/common.groovy new file mode 100644 index 000000000..e1f811e58 --- /dev/null +++ b/config/groovy/common.groovy @@ -0,0 +1,354 @@ +import groovy.json.JsonSlurper + +@GrabResolver(name = 'jcenter', root = 'http://jcenter.bintray.com/') +@Grab(group = 'org.ajoberstar', module = 'grgit', version = '1.9.3') +import org.ajoberstar.grgit.Grgit +import org.ajoberstar.grgit.Remote + +class common { + + /** For preparing an instance of the target item type utility class we want to work with */ + GroovyObject itemTypeScript + + /** The official default GitHub home (org/user) for the type */ + def githubDefaultHome + + /** The actual target GitHub home (org/user) for the type, as potentially requested by the user */ + def githubTargetHome + + /** The target directory for the type */ + File targetDirectory + + /** 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 */ + def excludedItems + + /** For keeping a list of items retrieved so far */ + def itemsRetrieved = [] + + /** The default name of a git remote we might want to work on or keep handy */ + String defaultRemote = "origin" + + /** + * Initialize defaults to match the target item type + * @param type the type to be initialized + */ + def initialize(String type) { + // Look for a gradle.properties to check for a variety of override configuration + Properties properties = new Properties() + File gradlePropsFile = new File("gradle.properties") + if (gradlePropsFile.exists()) { + gradlePropsFile.withInputStream { + properties.load(it) + } + } + + File itemTypeScriptFile = new File("config/groovy/${type}.groovy") + Class targetClass = new GroovyClassLoader(getClass().getClassLoader()).parseClass(itemTypeScriptFile) + itemTypeScript = (GroovyObject) targetClass.newInstance() + + excludedItems = itemTypeScript.excludedItems + githubDefaultHome = itemTypeScript.getGithubDefaultHome(properties) + githubTargetHome = githubDefaultHome + targetDirectory = itemTypeScript.targetDirectory + itemType = itemTypeScript.itemType + } + + /** + * Accepts input from the user, showing a descriptive prompt. + * @param prompt the prompt to show the user + */ + def getUserString(String prompt) { + println('\n*** ' + prompt + '\n') + def reader = new BufferedReader(new InputStreamReader(System.in)) + return reader.readLine() + } + + /** + * Tests a URL via a HEAD request (no body) to see if it is valid + * @param url the URL to test + * @return boolean indicating whether the URL is valid (code 200) or not + */ + boolean isUrlValid(String url) { + def code = new URL(url).openConnection().with { + requestMethod = 'HEAD' + connect() + responseCode + } + return code.toString() == "200" + } + + /** + * Primary entry point for retrieving items, kicks off recursively if needed. + * @param items the items we want to retrieve + */ + def retrieve(String[] items) { + println "Now inside retrieve, user wants: $items" + for (String itemName: items) { + println "Starting retrieval for $itemType $itemName" + println "Retrieved so far: $itemsRetrieved" + retrieveItem(itemName) + } + } + + /** + * Retrieves a single item via Git Clone. Considers whether it exists locally first or if it has already been retrieved this execution. + * @param itemName the target item to retrieve + */ + def retrieveItem(String itemName) { + File targetDir = new File(targetDirectory, itemName) + println "Request to retrieve $itemType $itemName would store it at $targetDir - exists? " + targetDir.exists() + if (targetDir.exists()) { + println "That $itemType already had an existing directory locally. If something is wrong with it please delete and try again" + itemsRetrieved << itemName + } else if (itemsRetrieved.contains(itemName)) { + println "We already retrieved $itemName - skipping" + } else { + itemsRetrieved << itemName + def targetUrl = "https://github.com/${githubTargetHome}/${itemName}" + if (!isUrlValid(targetUrl)) { + println "Can't retrieve $itemType from $targetUrl - URL appears invalid. Typo? Not created yet?" + return + } + println "Retrieving $itemType $itemName from $targetUrl" + 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 + } + + // This step allows the item type to check the newly cloned item and add in extra template stuff + itemTypeScript.copyInTemplateFiles(targetDir) + + def foundDependencies = itemTypeScript.findDependencies(targetDir) + if (foundDependencies.length == 0) { + println "The $itemType $itemName did not appear to have any dependencies we need to worry about" + } else { + println "The $itemType $itemName has the following $itemType dependencies we care about: $foundDependencies" + String[] uniqueDependencies = foundDependencies - itemsRetrieved + println "After removing dupes already retrieved we have the remaining dependencies left: $uniqueDependencies" + if (uniqueDependencies.length > 0) { + retrieve(uniqueDependencies, true) + } + } + + } + } + + /** + * Creates a new item with the given name and adds the necessary .gitignore file plus more if the itemType desires + * @param itemName the name of the item to be created + */ + def createItem(String itemName) { + File targetDir = new File(targetDirectory, itemName) + if (targetDir.exists()) { + println "Target directory already exists. Aborting." + return + } + println "Creating target directory" + targetDir.mkdir() + + 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") + def gitignoreText = new File("templates/.gitignore").text + gitignore << gitignoreText + + itemTypeScript.copyInTemplateFiles(targetDir) + + Grgit.init dir: targetDir, bare: false + addRemote(itemName, defaultRemote, "https://github.com/${githubDefaultHome}/${itemName}.git") + } + + /** + * Update a given item. + * @param itemName the name of the item to update + */ + def updateItem(String itemName) { + println "Attempting to update $itemType $itemName" + File targetDir = new File(targetDirectory, itemName) + if (!targetDir.exists()) { + println "$itemType \"$itemName\" not found" + 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" + 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 + } + println "Updating $itemType $itemName" + itemGit.pull remote: defaultRemote + } + + /** + * List all existing Git remotes for a given item. + * @param itemName the item to list remotes for + */ + def listRemotes(String itemName) { + if (!new File(targetDirectory, itemName).exists()) { + println "$itemType '$itemName' not found. Typo? Or run 'groovyw $itemType get $itemName' first" + return + } + def remoteGit = Grgit.open(dir: "${targetDirectory}/${itemName}") + def remote = remoteGit.remote.list() + def index = 1 + for (Remote item: remote) { + println(index + " " + item.name + " (" + item.url + ")") + index++ + } + } + + /** + * Add new Git remotes for the given items, all using the same remote name. + * @param items the items to add remotes for + * @param name the name to use for all the Git remotes + */ + def addRemotes(String[] items, String name) { + for (String item : items) { + addRemote(item, name) + } + } + + /** + * Add a new Git remote for the given item, deducing a standard URL to the repo. + * @param itemName the item to add the remote for + * @param remoteName the name to give the new remote + */ + def addRemote(String itemName, String remoteName) { + addRemote(itemName, remoteName, "https://github.com/$remoteName/$itemName" + ".git") + } + + /** + * Add a new Git remote for the given item. + * @param itemName the item to add the remote for + * @param remoteName the name to give the new remote + * @param URL address to the remote Git repo + */ + def addRemote(String itemName, String remoteName, String url) { + File targetModule = new File(targetDirectory, itemName) + if (!targetModule.exists()) { + println "$itemType '$itemName' not found. Typo? Or run 'groovyw $itemType get $itemName' first" + return + } + def remoteGit = Grgit.open(dir: "${targetDirectory}/${itemName}") + def remote = remoteGit.remote.list() + def check = remote.find { + it.name == "$remoteName" + } + if (!check) { + remoteGit.remote.add(name: "$remoteName", url: "$url") + if (isUrlValid(url)) { + println "Successfully added remote '$remoteName' for '$itemName' - doing a 'git fetch'" + remoteGit.fetch remote: remoteName + } else { + 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" + } + } + + /** + * Considers given arguments for the presence of a custom remote, setting that up right if found, tidying up the arguments. + * @param arguments the args passed into the script + * @return the adjusted arguments without any found custom remote details and the commmand name itself + */ + def processCustomRemote(String[] arguments) { + def remoteArg = arguments.findLastIndexOf { + it == "-remote" + } + if (remoteArg != -1) { + if (arguments.length == (remoteArg + 1)) { + githubTargetHome = getUserString('Enter name for the git remote (no spaces)') + arguments = arguments.dropRight(1) + } else { + githubTargetHome = arguments[remoteArg + 1] + arguments = arguments.dropRight(2) + } + } + return arguments.drop(1) + } + + /** + * Retrieves all the available items for the target type in the form of a list. + * @return a String[] containing the names of items available for download. + */ + String[] retrieveAvailableItems() { + + // 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" + + if(!isUrlValid(githubHomeApiUrl)){ + println "Deduced GitHub API URL $githubHomeApiUrl seems inaccessible." + return [] + } + + // Make a temporary map of found repos (possible items) and the associated repo description (for filter options) + def mappedPossibleItems = [:] + def currentPageUrl = githubHomeApiUrl + def slurper = new JsonSlurper() + while (currentPageUrl) { + new URL(currentPageUrl).openConnection().with { connection -> + connection.content.withReader { reader -> + slurper.parseText(reader.text).each { item -> + mappedPossibleItems.put(item.name, item.description) + } + } + currentPageUrl = getLink(connection, "next") + } + } + + return itemTypeScript.filterItemsFromApi(mappedPossibleItems) + } + + /** + * Retrieves link from HTTP headers (RFC 5988). + * @param connection connection to retrieve link from + * @param relation relation type of requested link + * @return link with the requested relation type + */ + private static String getLink(URLConnection connection, String relation) { + def links = connection.getHeaderField("Link") + def linkMatcher = links =~ /<(.*)>;\s*rel="${relation}"/ + linkMatcher.find() ? linkMatcher.group(1) : null + } + + /** + * Retrieves all the downloaded items in the form of a list. + * @return a String[] containing the names of downloaded items. + */ + String[] retrieveLocalItems(){ + def localItems =[] + targetDirectory.eachDir() { dir -> + String itemName = dir.getName() + // Don't consider excluded items + if(!(excludedItems.contains(itemName))){ + localItems << itemName + } + } + return localItems + } +} diff --git a/config/groovy/module.groovy b/config/groovy/module.groovy new file mode 100644 index 000000000..8aa94c02d --- /dev/null +++ b/config/groovy/module.groovy @@ -0,0 +1,88 @@ +import groovy.json.JsonSlurper + +class module { + +def excludedItems = ["core","DestinationSol.github.io"] + +def getGithubDefaultHome(Properties properties) { + return properties.alternativeGithubHome ?: "DestinationSol" +} + +File targetDirectory = new File("modules") +def itemType = "module" + +String[] findDependencies(File targetDir) { + def foundDependencies = readModuleDependencies(new File(targetDir, "module.txt")) + println "Looked for dependencies, found: " + foundDependencies + return foundDependencies +} + +/** + * 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) + * @return a String[] containing the next level of dependencies, if any + */ +String[] readModuleDependencies(File targetModuleInfo) { + def qualifiedDependencies = [] + if (!targetModuleInfo.exists()) { + println "The module info file did not appear to exist - can't calculate dependencies" + return qualifiedDependencies + } + def slurper = new JsonSlurper() + def moduleConfig = slurper.parseText(targetModuleInfo.text) + for (dependency in moduleConfig.dependencies) { + if (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" + qualifiedDependencies << dependency.id + } + } + return qualifiedDependencies +} + +def copyInTemplateFiles(File targetDir) { + // 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" + } +} + +/** + * Creates the directory structure of a destsol module + * @param root The root of the newly created module + */ +def createDirectoryStructure(File root) { + + // Set root to the assets folder for simplicity sake + root = new File(root, "assets") + root.mkdir() + + // Just create the base directories for them. They can organize them how they want after that + String[] dirs = ["configs", "emitters", "items", "ships", "sounds", "textures"] + + for (String dir : dirs) { + new File(root, dir).mkdir() + } +} + +/** + * 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 = [] + + // Modules just consider the item name and excludes those in a specific list + itemList = possibleItems.findAll { + !excludedItems.contains (it.key) + }.collect {it.key} + + return itemList +} +} diff --git a/config/groovy/util.groovy b/config/groovy/util.groovy new file mode 100644 index 000000000..57a1e31e0 --- /dev/null +++ b/config/groovy/util.groovy @@ -0,0 +1,216 @@ +// Create an instance of the common script to operate on +File sourceFile = new File("config/groovy/common.groovy") +Class sourceClass = new GroovyClassLoader(getClass().getClassLoader()).parseClass(sourceFile) +GroovyObject common = (GroovyObject) sourceClass.newInstance() + +// User didn't enter a type (example: `groovyw`) +if (args.length == 0) { + println "You need to supply some parameters! See 'groovyw usage' for details" + return +} + +// If the user literally requested usage text then print it! +if (args[0] == "usage") { + printUsage() + return +} + +// User entered a type that doesn't correspond to a script (example: `groovyw fish`) +if (!new File("config/groovy/" + args[0] + ".groovy").exists()) { + println "That type '" + args[0] + "' did not correspond to a defined utility type script. Typo? See 'groovyw usage'" + return +} + +// User didn't enter a sub-command (example: `groovyw module`) +if (args.length == 1) { + println "You need to supply a sub-command as well as a type of object to act on. See 'groovyw usage' for details" + return +} + +// Initialize the type target to load in things specific to that type +common.initialize(args[0]) +itemType = common.itemType + +// At this point we have the type script loaded, so don't need that arg anymore +String[] cleanerArgs = args.drop(1) + +excludedItems = common.excludedItems +targetDirectory = common.targetDirectory + +switch(cleanerArgs[0]) { + case "get": + println "Preparing to get $itemType" + if (cleanerArgs.length == 1) { + def itemString = common.getUserString("Enter what to get - separate multiple with spaces, CapiTaliZation MatterS): ") + println "User wants: $itemString" + String[] itemList = itemString.split("\\s+") + common.retrieve itemList + } else { + // Note: processCustomRemote also drops one of the array elements from cleanerArgs + cleanerArgs = common.processCustomRemote(cleanerArgs) + common.retrieve cleanerArgs + } + break + + case "create": + println "We're doing a create" + String name + if (cleanerArgs.length > 2) { + println "Received more than one argument. Aborting." + break + } else if (cleanerArgs.length == 2) { + // User already submitted the name of what they want to create + name = cleanerArgs[1] + } else { + // User hasn't entered a name yet so request it + name = getUserString("Enter $itemType name: ") + } + println "User wants to create a $itemType named: $name" + common.createItem(name) + println "Created $itemType named $name" + break + + case "update": + println "We're updating $itemType" + String[] itemList + if (cleanerArgs.length == 1) { + def itemString = common.getUserString("Enter what to update - separate multiple with spaces, CapiTaliZation MatterS): ") + itemList = itemString.split("\\s+") + } else { + itemList = cleanerArgs.drop(1) + } + println "List of items to update: $itemList" + for (String item : itemList) { + common.updateItem(item) + } + break + + case "update-all": + println "We're updating every $itemType" + println "List of local entries: ${common.retrieveLocalItems()}" + for(item in common.retrieveLocalItems()){ + common.updateItem(item) + } + break + + case "add-remote": + if (cleanerArgs.length == 3) { + itemName = cleanerArgs[1] + remoteName = cleanerArgs[2] + println "Adding git remote for $itemType $itemName" + common.addRemote(itemName, remoteName) + } else if (cleanerArgs.length == 4) { + itemName = cleanerArgs[1] + remoteName = cleanerArgs[2] + url = cleanerArgs[3] + println "Adding git remote for $itemType $itemName" + common.addRemote(itemName, remoteName, url) + } else { + println "Incorrect syntax" + println "Usage: 'groovyw ${itemType} add-remote [${itemType} name] [remote name]' - adds a git remote 'name' to the stated ${itemType} with default URL." + println " 'groovyw ${itemType} add-remote [${itemType} name] [remote name] [url]' - adds a git remote 'name' to the stated ${itemType} with the given URL." + } + break + + case "list-remotes": + if (cleanerArgs.length == 2) { + itemName = cleanerArgs[1] + println "Listing git remotes for $itemType $itemName" + common.listRemotes(itemName) + } else { + println "Incorrect syntax" + println "Usage: 'groovyw ${itemType} list-remotes [${itemType} name]' - lists all git remotes for that ${itemType}" + } + 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." + } else { + printListItems(downloadableItems, listFormat) + } + println "\nThe following items are already downloaded:" + if(localItems.size() == 0) { + println "No items downloaded." + } else { + printListItems(localItems, listFormat) + } + break + + default: + println "UNRECOGNIZED COMMAND '" + cleanerArgs[0] + "' - please try again or use 'groovyw usage' for help" +} + +enum ListFormat { DEFAULT, SIMPLE, CONDENSED }; + +private ListFormat determineListFormat(String[] args) { + for (listFormat in ListFormat.values()) { + if (args.contains("-${listFormat.name().toLowerCase()}-list-format")) + return listFormat; + } + return ListFormat.DEFAULT; +} + +private void printListItems(String[] items, ListFormat listFormat) { + def final DEFAULT_FORMAT_CONDENSATION_THRESHOLD = 50 + switch (listFormat) { + case ListFormat.SIMPLE: printListItemsSimple(items); break; + case ListFormat.CONDENSED: printListItemsCondensed(items); break; + default: items.size() < DEFAULT_FORMAT_CONDENSATION_THRESHOLD ? + printListItemsSimple(items) : + printListItemsCondensed(items) + } +} + +private void printListItemsSimple(String[] items) { + for (item in items.sort()) { + println "--$item" + } +} + +private void printListItemsCondensed(String[] items) { + for (group in items.groupBy {it[0].toUpperCase()}) { + println "--" + group.key + ": " + group.value.sort().join(", ") + } +} + +/** + * Simply prints usage information. + */ +def printUsage() { + println "" + println "Utility script for interacting with modules. Available sub commands:" + println "- 'get' - retrieves one or more modules in source form (separate with spaces)" + println "- 'list' - lists items that are available for download or downloaded already." + println "- 'create' - creates a new module" + println "- 'update' - updates a module (git pulls latest from current origin, if workspace is clean" + println "- 'update-all' - updates all local modules" + println "- 'add-remote (module) (name)' - adds a remote (name) to modules/(module) with the default URL." + println "- 'add-remote (module) (name) (URL)' - adds a remote with the given URL" + println "- 'list-remotes (module)' - lists all remotes for (module) " + 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' 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 "" + println "*NOTE*: Module names are case sensitive" + println "" + println "If you omit further arguments beyond the sub command you'll be prompted for details" + println "After changing modules available in your workspace rerun 'gradlew idea' and/or refresh your IDE" + 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 "" +} diff --git a/groovyw b/groovyw index 757080f7b..b67153763 100755 --- a/groovyw +++ b/groovyw @@ -162,7 +162,7 @@ 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 "$APP_ARGS" +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 diff --git a/groovyw.bat b/groovyw.bat index c437ccabc..151c1a226 100644 --- a/groovyw.bat +++ b/groovyw.bat @@ -66,7 +66,7 @@ set CMD_LINE_ARGS=%* 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 %CMD_LINE_ARGS% +"%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% :end @rem End local scope for the variables with windows NT shell diff --git a/module.groovy b/module.groovy deleted file mode 100644 index 4e914bbed..000000000 --- a/module.groovy +++ /dev/null @@ -1,512 +0,0 @@ -// We use GrGit for interacting with Git. This gets a hold of it as a dependency like Gradle would -// TODO: Consider if we should do something to fix/suppress the SLF4J warning that gets logged on first usage? -@GrabResolver(name = 'jcenter', root = 'http://jcenter.bintray.com/') -@Grab(group='org.ajoberstar', module='grgit', version='1.9.3') -import org.ajoberstar.grgit.Grgit -import org.ajoberstar.grgit.Remote -import groovy.json.JsonSlurper - -// Grab override properties from the gradle.properties file (shared with various Gradle commands) -Properties properties = new Properties() -new File("gradle.properties").withInputStream { - properties.load(it) -} - -// Groovy Elvis operator woo! Defaults to "DestinationSol" if an override isn't set -githubHome = properties.alternativeGithubHome ?: "DestinationSol" - -//println "githubHome is: $githubHome" - -// Module dependencies we don't want to retrieve as they live in the main DestSol repo -excludedDependencies = ["core"] - -// For keeping a list of modules retrieved so far -modulesRetrieved = [] - -/** - * Primary entry point for retrieving modules, kicks off recursively if needed. - * @param modules the modules we want to retrieve - */ -def retrieve(String[] modules) { - for (String module : modules) { - println "Starting get for module $module." - println "Modules retrieved so far: $modulesRetrieved" - retrieveModule(module) - //println "Modules retrieved after recent addition(s): modulesRetrieved" - } -} - -/** - * Tests a URL via a HEAD request (no body) to see if it is valid - * @param url the URL to test - * @return boolean indicating whether the URL is valid (code 200) or not - */ -boolean isUrlValid(String url) { - def code = new URL(url).openConnection().with { - requestMethod = 'HEAD' - connect() - responseCode - } - return code.toString() == "200" -} - -/** - * Retrieves all the available modules on DestinationSol account in the form of a list. - * @return a String[] containing the names of modules available for downlaod. - */ - String[] retrieveAvailableModules() { - def moduleList = [] - def githubAPIURL = "https://api.github.com/users/DestinationSol/repos" - if(!isUrlValid(githubAPIURL)){ - println "Some problem in retrieving available modules. GitHub API not accessible." - return - } - def repoData = new URL(githubAPIURL).getText() - def slurper = new JsonSlurper() - def parsedData = slurper.parseText(repoData) - for (repo in parsedData.name) { - //DestinationSol account contains the splash site. Its not a module so exclude it. - if (!(repo=="DestinationSol.github.io")) { - moduleList << repo - } - } - return moduleList - } - - /** - * Retrieves all the downloaded modules in the form of a list. - * @return a String[] containing the names of downloaded modules. - */ -String[] retrieveLocalModules(){ - def localModules =[] - new File("modules").eachDir() { dir -> - String moduleName = dir.getPath().substring(8) - //The excludedDependencies are not considered as downloaded modules. - if(!(excludedDependencies.contains(moduleName))){ - localModules << moduleName - } - } - return localModules -} - -/** - * Reads a given module info file to figure out which if any dependencies it has. Filters out any already retrieved. - * @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) { - def qualifiedDependencies = [] - if (!targetModuleInfo.exists()) { - println "The module info file did not appear to exist - can't calculate dependencies" - return qualifiedDependencies - } - - def slurper = new JsonSlurper() - def moduleConfig = slurper.parseText(targetModuleInfo.text) - for (dependency in moduleConfig.dependencies) { - if (excludedDependencies.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" - qualifiedDependencies << dependency.id - } - } - return qualifiedDependencies -} - -/** - * Retrieves a single module via Git Clone. Considers whether it exists locally first or if it has already been retrieved this execution. - * @param module the target module to retrieve - */ -def retrieveModule(String module) { - File targetDir = new File("modules/$module") - println "Request to retrieve module $module would store it at $targetDir - exists? " + targetDir.exists() - - if (targetDir.exists()) { - println "That module already had an existing directory locally. If something is wrong with it please delete and try again" - modulesRetrieved << module - } else if (modulesRetrieved.contains(module)) { - println "We already retrieved $module - skipping" - } else { - // Immediately note the given module as retrieved, since if any failure occurs we don't want to retry - modulesRetrieved << module - def targetUrl = "https://github.com/$githubHome/${module}" - if (!isUrlValid(targetUrl)) { - println "Can't retrieve module from $targetUrl - URL appears invalid. Typo? Not created yet?" - return - } - println "Retrieving module $module - if it doesn't appear to exist (typo for instance) you'll get an auth prompt (in case it is private)" - // Prepare to clone the target repo, adding a secondary remote if it isn't already hosted under the DestinationSol org - if (githubHome != "DestinationSol") { - println "Doing a retrieve from a custom remote: $githubHome - will name it as such plus add the DestinationSol remote as 'origin'" - //noinspection GroovyAssignabilityCheck - GrGit has its own .clone but a warning gets issued for Object.clone - Grgit.clone dir: targetDir, uri: targetUrl, remote: githubHome - println "Primary clone operation complete, about to add the 'origin' remote for the DestinationSol org address" - addRemote(module, "origin", "https://github.com/DestinationSol/${module}") - } else { - //noinspection GroovyAssignabilityCheck - GrGit has its own .clone but a warning gets issued for Object.clone - Grgit.clone dir: targetDir, uri: targetUrl - } - - File moduleManifest = new File(targetDir, 'module.json') - if (!moduleManifest.exists()) { - def moduleText = new File("templates/module.json").text - moduleManifest << moduleText.replaceAll('MODULENAME', module) - println "WARNING: Module $module did not have a module.json! One was created, please review and submit to GitHub" - } - def foundDependencies = readModuleDependencies(new File(targetDir, "module.json")) - if (foundDependencies.length == 0) { - println "Module $module did not appear to have any dependencies we need to worry about" - } else { - println "Module $module has the following module dependencies we care about: $foundDependencies" - String[] uniqueDependencies = foundDependencies - modulesRetrieved - println "After removing dupes already retrieved we have the remaining dependencies left: $uniqueDependencies" - if (uniqueDependencies.length > 0) { - retrieve(uniqueDependencies) - } - } - } -} - -/** - * Creates a new module with the given name and adds the necessary .gitignore, - * and module.json files. - * @param name the name of the module to be created - */ -def createModule(String name) { - // Check if the module already exists. If not, create the module directory - File targetDir = new File("modules/$name") - if (targetDir.exists()) { - println "Target directory already exists. Aborting." - return - } - println "Creating target directory" - targetDir.mkdir() - - createDirectoryStructure(targetDir) - - // Add gitignore - println "Creating .gitignore" - File gitignore = new File(targetDir, ".gitignore") - def gitignoreText = new File("templates/.gitignore").text - gitignore << gitignoreText - - // Add module.json - println "Creating module.json" - File moduleManifest = new File(targetDir, "module.json") - def moduleText = new File("templates/module.json").text - moduleManifest << moduleText.replaceAll('MODULENAME', name) - - // Initialize git - Grgit.init dir: targetDir, bare: false - addRemote(moduleName, "origin", "https://github.com/DestinationSol/${moduleName}.git") -} - -/** - * Creates the directory structure of a destsol module - * @param root The root of the newly created module - */ -def createDirectoryStructure(File root) { - - // Set root to the assets folder for simplicity sake - root = new File(root, "assets") - root.mkdir() - - // Just create the base directories for them. They can organize them how they want after that - String[] dirs = ["configs", "emitters", "items", "ships", "sounds", "textures"] - - for (String dir : dirs) { - new File(root, dir).mkdir() - } - -} - -/** - * Update a given module. - * @param name the name of the module to update - */ -def updateModule(String name) { - println "Attempting to update module $name" - File targetDir = new File("modules/$name") - if (!targetDir.exists()) { - println "Module \"$name\" not found" - return - } - - def moduleGit = Grgit.open(dir: targetDir) - def clean = moduleGit.status().clean - println "Is \"$name\" clean? $clean" - if (!clean) { - println "Module has uncommitted changes. Aborting." - return - } - - println "Updating module $name" - moduleGit.pull remote: "origin" -} - -/** - * Accepts input from the user, showing a descriptive prompt. - * @param prompt the prompt to show the user - */ -def getUserString (String prompt) { - println ('\n*** ' + prompt + '\n') - - def reader = new BufferedReader(new InputStreamReader(System.in)) // Note: Do not close reader, it will close System.in (Big no-no) - - return reader.readLine() -} - -/** - * List all existing Git remotes for a given module. - * @param moduleName the module to list remotes for - */ -def listRemotes(String moduleName) { - File moduleExistence = new File("modules/$moduleName") - if (!moduleExistence.exists()) { - println "Module '$moduleName' not found. Typo? Or run 'groovyw module get $moduleName' first" - return - } - def remoteGit = Grgit.open(dir: "modules/$moduleName") - def remote = remoteGit.remote.list() - x = 1 - for (Remote item : remote) { - println(x + " " + item.name + " " + "(" + item.url + ")") - x += 1 - } -} - - - -/** - * Add new Git remotes for the given modules, all using the same remote name. - * @param modules the modules to add remotes for - * @param name the name to use for all the Git remotes - */ -def addRemotes(String[] modules, String name) { - for (String module : modules) { - addRemote(module, name) - } -} - -/** - * Add a new Git remote for the given module, deducing a standard URL to the repo. - * @param moduleName the module to add the remote for - * @param remoteName the name to give the new remote - */ -def addRemote(String moduleName, String remoteName) { - addRemote(moduleName, remoteName, "https://github.com/$remoteName/$moduleName" + ".git") -} - - -/** - * Add a new Git remote for the given module. - * @param moduleName the module to add the remote for - * @param remoteName the name to give the new remote - * @param URL address to the remote Git repo - */ - -def addRemote(String moduleName, String remoteName, String url) { - File targetModule = new File("modules/$moduleName") - if (!targetModule.exists()) { - println "Module '$moduleName' not found. Typo? Or run 'groovyw module get $moduleName' first" - return - } - def remoteGit = Grgit.open(dir: "modules/$moduleName") - def remote = remoteGit.remote.list() - def check = remote.find { it.name == "$remoteName" } - if (!check) { - // Always add the remote whether it exists or not - remoteGit.remote.add(name: "$remoteName", url: "$url") - // But then do a validation check to advise the user and do a fetch if it is valid - if (isUrlValid(url)) { - println "Successfully added remote '$remoteName' for '$moduleName' - doing a 'git fetch'" - remoteGit.fetch remote: remoteName - } else { - println "Added the remote '$remoteName' for module '$moduleName' - but the URL $url failed a test lookup. Typo? Not created yet?" - } - } else { - println "Remote already exists" - } -} - -/** - * Considers given arguments for the presence of a custom remote, setting that up right if found, tidying up the arguments. - * @param arguments the args passed into the script - * @return the adjusted arguments without any found custom remote details and the commmand name itself (get or recurse) - */ -def processCustomRemote(String[] arguments) { - def remoteArg = arguments.findLastIndexOf { it == "-remote" } - - // If we find the remote arg go ahead and process it then remove the related arguments - if (remoteArg != -1) { - // If the user didn't we can tell by simply checking the number of elements vs where "-remote" was - if (arguments.length == (remoteArg + 1)) { - githubHome = getUserString('Enter Name for the Remote (no spaces)') - // Drop the "-remote" so the arguments string gets cleaner - arguments = arguments.dropRight(1) - } else { - githubHome = arguments[remoteArg + 1] - // Drop the "-remote" as well as the value the user supplied - arguments = arguments.dropRight(2) - } - } - return arguments.drop(1) -} - -/** - * Simply prints usage information. - */ -def printUsage() { - println "" - println "Utility script for interacting with modules. Available sub commands:" - println "- 'list-modules' - lists modules that are available for download or downloaded already." - println "- 'get' - retrieves one or more modules in source form (separate with spaces)" - println "- 'create' - creates a new module" - println "- 'update' - updates a module (git pulls latest from current origin, if workspace is clean" - println "- 'update-all' - updates all local modules" - println "- 'add-remote (module) (name)' - adds a remote (name) to modules/(module) with the default URL." - println "- 'add-remote (module) (name) (URL)' - adds a remote with the given URL" - println "- 'list-remotes (module)' - lists all remotes for (module) " - println "" - println "Available flags" - println "-remote [someRemote]' to clone from an alternative remote, also adding the DestinationSol repo as 'origin'" - 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 "*NOTE*: Module names are case sensitive" - println "" - println "If you omit further arguments beyond the sub command you'll be prompted for details" - println "After changing modules available in your workspace rerun 'gradlew idea' and/or refresh your IDE" - 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 "" -} - -// Main bit of logic handling the entry points to this script - defers actual work to dedicated methods -// println "Args: $args" -if (args.length == 0) { - printUsage() -} else { - switch (args[0]) { - case 'usage': - printUsage() - break - case "get": - println "Preparing to get one or more modules" - if (args.length == 1) { - // User hasn't supplied any module names, so ask - def moduleString = getUserString('Enter Module Name(s - separate multiple with spaces, CapiTaliZation MatterS): ') - println "User wants: $moduleString" - // Split it on whitespace - String[] moduleList = moduleString.split("\\s+") - println "Now in an array: $moduleList" - retrieve moduleList - } else { - // First see if the user included "-remote" and process that if so. Expect a clean array back - args = processCustomRemote(args) - retrieve args - } - println "All done retrieving requested modules: $modulesRetrieved" - break - case "create": - println "We're doing a create" - String name = "" - - // Get new module's name - if (args.length > 2) { - println "Received more than one argument. Aborting." - break - } else if (args.length == 2) { - name = args[1] - } else { - name = getUserString("Enter module name: ") - } - println "User wants to create a module named: $name" - - createModule(name) - - println "Created module named $name" - break - case "update": - println "We're updating modules" - String[] moduleList = [] - if (args.length == 1) { - // User hasn't supplied any module names, so ask - def moduleString = getUserString('Enter Module Name(s - separate multiple with spaces, CapiTaliZation MatterS): ') - // Split it on whitespace - moduleList = moduleString.split("\\s+") - } else { - // User has supplied one or more module names, so pass them forward (skipping the "get" arg) - moduleList = args.drop(1) - } - println "List of modules to update: $moduleList" - for (String module: moduleList) { - updateModule(module) - } - break - case "update-all": - println "We're updating all modules" - println "List of modules: ${retrieveLocalModules()}" - for(module in retrieveLocalModules()){ - updateModule(module) - } - break - case "add-remote": - if (args.length == 3) { - moduleName = args[1] - remoteName = args[2] - println "Adding Remote for module $moduleName" - addRemote(moduleName, remoteName) - } else if (args.length == 4) { - moduleName = args[1] - remoteName = args[2] - url = args[3] - println "Adding Remote for module $moduleName" - addRemote(moduleName, remoteName, url) - } else { - println "Incorrect Syntax" - println "Usage: 'add-remote (module) (name)' - adds a remote (name) to modules/(module) with default URL." - println " 'add-remote (module) (name) (url)' - adds a remote to the module with the given URL." - } - break - case "list-remotes": - if (args.length == 2) { - moduleName = args[1] - println "Listing Remotes for module $moduleName" - listRemotes(moduleName) - } else { - println "Incorrect Syntax" - println "Usage: 'list-remotes (module)' - lists all remotes for (module)" - } - break - case "list-modules": - String[] availableModules = retrieveAvailableModules() - String[] localModules = retrieveLocalModules() - println "The following modules are available for download:" - if (availableModules.size() == 0){ - println "No modules available for download." - } else if (localModules == availableModules){ - println "All modules are downloaded." - } else { - for (module in availableModules){ - if(!(localModules.contains(module))){ - println "--$module" - } - } - } - println "" - println "The following modules are already downloaded:" - if(localModules.size() == 0){ - println "No modules downloaded." - } else { - for(module in localModules){ - println "--$module" - } - } - break - default: - println "UNRECOGNIZED COMMAND - please try again or use 'groovyw module usage' for help" - } -}