From 2956934222c581793c65f6dbefc45c1001c3f0a6 Mon Sep 17 00:00:00 2001 From: burtbeckwith Date: Thu, 16 Jun 2011 01:33:50 -0400 Subject: [PATCH] got console coloring working in Windows - was missing jna.jar and needed some fixes in jline.WindowsTerminal --- build.gradle | 2 + grails-bootstrap/build.gradle | 3 +- .../grails/build/logging/GrailsConsole.java | 32 +- .../main/groovy/jline/WindowsTerminal.java | 536 ++++++++++++++++++ .../resolve/GrailsCoreDependencies.java | 1 + .../src/grails/home/conf/groovy-starter.conf | 1 + .../resolve/IvyDependencyManagerTests.groovy | 6 +- 7 files changed, 576 insertions(+), 5 deletions(-) create mode 100644 grails-bootstrap/src/main/groovy/jline/WindowsTerminal.java diff --git a/build.gradle b/build.gradle index 1aedbb36b05..53fd2a84d46 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ gantVersion = "1.9.5" groovyVersion = "1.8.0" jansiVersion = "1.2.1" jlineVersion = "0.9.94" +jnaVersion = "3.2.3" commonsCliVersion = "1.2" commonsCollectionsVersion = "3.2.1" commonsBeanUtilsVersion = "1.8.0" @@ -237,6 +238,7 @@ task installResources << { filter(ReplaceTokens, tokens: ['groovy.version': groovyVersion]) filter(ReplaceTokens, tokens: ['jline.version': jlineVersion]) filter(ReplaceTokens, tokens: ['jansi.version': jansiVersion]) + filter(ReplaceTokens, tokens: ['jna.version': jnaVersion]) filter(ReplaceTokens, tokens: ['commons.cli.version': commonsCliVersion]) filter(ReplaceTokens, tokens: ['ant.version': antVersion]) filter(ReplaceTokens, tokens: ['gant.version': gantVersion]) diff --git a/grails-bootstrap/build.gradle b/grails-bootstrap/build.gradle index 51d8e7a6a97..d707ad21dab 100644 --- a/grails-bootstrap/build.gradle +++ b/grails-bootstrap/build.gradle @@ -26,7 +26,8 @@ dependencies { // Ant compile 'org.fusesource.jansi:jansi:1.2.1', - 'jline:jline:0.9.94' + 'jline:jline:0.9.94', + 'net.java.dev.jna:jna:3.2.3' compile "org.apache.ant:ant:${antVersion}", "org.apache.ant:ant-launcher:${antVersion}", diff --git a/grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java b/grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java index d10f3685050..28ee257f1d7 100644 --- a/grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java +++ b/grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java @@ -31,6 +31,9 @@ import jline.ConsoleReader; import jline.Terminal; +import jline.UnixTerminal; +import jline.UnsupportedTerminal; +import jline.WindowsTerminal; import org.codehaus.groovy.grails.cli.interactive.CandidateListCompletionHandler; import org.codehaus.groovy.grails.cli.logging.GrailsConsolePrintStream; @@ -103,7 +106,7 @@ protected GrailsConsole() throws IOException { System.setOut(new GrailsConsolePrintStream(this.out)); - terminal = Terminal.setupTerminal(); + terminal = setupTerminal(); reader = new ConsoleReader(); reader.setCompletionHandler(new CandidateListCompletionHandler()); category.add("grails"); @@ -113,6 +116,33 @@ protected GrailsConsole() throws IOException { out.println(); } + // copied from Terminal.setupTerminal() + private Terminal setupTerminal() { + final Terminal t; + + String os = System.getProperty("os.name").toLowerCase(); + if (os.indexOf("windows") != -1) { + t = new WindowsTerminal() { + @Override + public boolean isANSISupported() { + return true; + }; + }; + } + else { + t = new UnixTerminal(); + } + + try { + t.initializeTerminal(); + return t; + } + catch (Exception e) { + e.printStackTrace(); + return new UnsupportedTerminal(); + } + } + public static synchronized GrailsConsole getInstance() { if (instance == null) { try { diff --git a/grails-bootstrap/src/main/groovy/jline/WindowsTerminal.java b/grails-bootstrap/src/main/groovy/jline/WindowsTerminal.java new file mode 100644 index 00000000000..9b0a6e3facc --- /dev/null +++ b/grails-bootstrap/src/main/groovy/jline/WindowsTerminal.java @@ -0,0 +1,536 @@ +package jline; + +/* + * This is a patched version based on Joris Kuipers' work for https://jira.springsource.org/browse/ROO-350 + * Version 1.0 has these fixes but isn't available at Maven Central yet. + */ + +/* + * Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved. + * + * This software is distributable under the BSD license. See the terms of the + * BSD license in the documentation provided with this software. + */ + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; + +import jline.Terminal; +import jline.WindowsTerminal; + +/** + *

+ * Terminal implementation for Microsoft Windows. Terminal initialization in + * {@link #initializeTerminal} is accomplished by extracting the + * jline_version.dll, saving it to the system temporary + * directoy (determined by the setting of the java.io.tmpdir System + * property), loading the library, and then calling the Win32 APIs SetConsoleMode and + * GetConsoleMode to + * disable character echoing. + *

+ * + *

+ * By default, the {@link #readCharacter} method will attempt to test to see if + * the specified {@link InputStream} is {@link System#in} or a wrapper around + * {@link FileDescriptor#in}, and if so, will bypass the character reading to + * directly invoke the readc() method in the JNI library. This is so the class + * can read special keys (like arrow keys) which are otherwise inaccessible via + * the {@link System#in} stream. Using JNI reading can be bypassed by setting + * the jline.WindowsTerminal.disableDirectConsole system property + * to true. + *

+ * + * @author Marc Prud'hommeaux + */ +public class WindowsTerminal extends Terminal { + // constants copied from wincon.h + + /** + * The ReadFile or ReadConsole function returns only when a carriage return + * character is read. If this mode is disable, the functions return when one + * or more characters are available. + */ + private static final int ENABLE_LINE_INPUT = 2; + + /** + * Characters read by the ReadFile or ReadConsole function are written to + * the active screen buffer as they are read. This mode can be used only if + * the ENABLE_LINE_INPUT mode is also enabled. + */ + private static final int ENABLE_ECHO_INPUT = 4; + + /** + * CTRL+C is processed by the system and is not placed in the input buffer. + * If the input buffer is being read by ReadFile or ReadConsole, other + * control keys are processed by the system and are not returned in the + * ReadFile or ReadConsole buffer. If the ENABLE_LINE_INPUT mode is also + * enabled, backspace, carriage return, and linefeed characters are handled + * by the system. + */ + private static final int ENABLE_PROCESSED_INPUT = 1; + + /** + * User interactions that change the size of the console screen buffer are + * reported in the console's input buffee. Information about these events + * can be read from the input buffer by applications using + * theReadConsoleInput function, but not by those using ReadFile + * orReadConsole. + */ + private static final int ENABLE_WINDOW_INPUT = 8; + + /** + * If the mouse pointer is within the borders of the console window and the + * window has the keyboard focus, mouse events generated by mouse movement + * and button presses are placed in the input buffer. These events are + * discarded by ReadFile or ReadConsole, even when this mode is enabled. + */ + private static final int ENABLE_MOUSE_INPUT = 16; + + /** + * When enabled, text entered in a console window will be inserted at the + * current cursor location and all text following that location will not be + * overwritten. When disabled, all following text will be overwritten. An OR + * operation must be performed with this flag and the ENABLE_EXTENDED_FLAGS + * flag to enable this functionality. + */ + private static final int ENABLE_PROCESSED_OUTPUT = 1; + + /** + * This flag enables the user to use the mouse to select and edit text. To + * enable this option, use the OR to combine this flag with + * ENABLE_EXTENDED_FLAGS. + */ + private static final int ENABLE_WRAP_AT_EOL_OUTPUT = 2; + + /** + * On windows terminals, this character indicates that a 'special' key has + * been pressed. This means that a key such as an arrow key, or delete, or + * home, etc. will be indicated by the next character. + */ + public static final int SPECIAL_KEY_INDICATOR = 224; + + /** + * On windows terminals, this character indicates that a special key on the + * number pad has been pressed. + */ + public static final int NUMPAD_KEY_INDICATOR = 0; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, + * this character indicates an left arrow key press. + */ + public static final int LEFT_ARROW_KEY = 75; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates an + * right arrow key press. + */ + public static final int RIGHT_ARROW_KEY = 77; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates an up + * arrow key press. + */ + public static final int UP_ARROW_KEY = 72; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates an + * down arrow key press. + */ + public static final int DOWN_ARROW_KEY = 80; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the delete key was pressed. + */ + public static final int DELETE_KEY = 83; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the home key was pressed. + */ + public static final int HOME_KEY = 71; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the end key was pressed. + */ + public static final char END_KEY = 79; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the page up key was pressed. + */ + public static final char PAGE_UP_KEY = 73; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the page down key was pressed. + */ + public static final char PAGE_DOWN_KEY = 81; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR + * this character indicates that + * the insert key was pressed. + */ + public static final char INSERT_KEY = 82; + + /** + * When following the SPECIAL_KEY_INDICATOR or NUMPAD_KEY_INDICATOR, + * this character indicates that the escape key was pressed. + */ + public static final char ESCAPE_KEY = 0; + + private Boolean directConsole; + + private boolean echoEnabled; + + String encoding = System.getProperty("jline.WindowsTerminal.input.encoding", System.getProperty("file.encoding")); + ReplayPrefixOneCharInputStream replayStream = new ReplayPrefixOneCharInputStream(encoding); + InputStreamReader replayReader; + + public WindowsTerminal() { + String dir = System.getProperty("jline.WindowsTerminal.directConsole"); + + if ("true".equals(dir)) { + directConsole = Boolean.TRUE; + } else if ("false".equals(dir)) { + directConsole = Boolean.FALSE; + } + + try { + replayReader = new InputStreamReader(replayStream, encoding); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private native int getConsoleMode(); + + private native void setConsoleMode(final int mode); + + private native int readByte(); + + private native int getWindowsTerminalWidth(); + + private native int getWindowsTerminalHeight(); + + public int readCharacter(final InputStream in) throws IOException { + // if we can detect that we are directly wrapping the system + // input, then bypass the input stream and read directly (which + // allows us to access otherwise unreadable strokes, such as + // the arrow keys) + if (directConsole == Boolean.FALSE) { + return super.readCharacter(in); + } else if ((directConsole == Boolean.TRUE) + || ((in == System.in) || (in instanceof FileInputStream + && (((FileInputStream) in).getFD() == FileDescriptor.in)))) { + return readByte(); + } else { + return super.readCharacter(in); + } + } + + public void initializeTerminal() throws Exception { + loadLibrary("jline"); + + final int originalMode = getConsoleMode(); + + setConsoleMode(originalMode & ~ENABLE_ECHO_INPUT); + + // set the console to raw mode + int newMode = originalMode + & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT + | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); + echoEnabled = false; + setConsoleMode(newMode); + + // at exit, restore the original tty configuration (for JDK 1.3+) + try { + Runtime.getRuntime().addShutdownHook(new Thread() { + public void start() { + // restore the old console mode + setConsoleMode(originalMode); + } + }); + } catch (AbstractMethodError ame) { + // JDK 1.3+ only method. Bummer. + consumeException(ame); + } + } + + private void loadLibrary(final String name) throws IOException { + // store the DLL in the temporary directory for the System + String version = WindowsTerminal.class.getPackage().getImplementationVersion(); + + if (version == null) { + version = ""; + } + + version = version.replace('.', '_'); + + File f = new File(System.getProperty("java.io.tmpdir"), name + "_" + + version + ".dll"); + boolean exists = f.isFile(); // check if it already exists + + // extract the embedded jline.dll file from the jar and save + // it to the current directory + int bits = 32; + + // check for 64-bit systems and use to appropriate DLL + if (System.getProperty("os.arch").indexOf("64") != -1) + bits = 64; + + InputStream in = new BufferedInputStream(WindowsTerminal.class.getResourceAsStream(name + bits + ".dll")); + + OutputStream fout = null; + try { + fout = new BufferedOutputStream( + new FileOutputStream(f)); + byte[] bytes = new byte[1024 * 10]; + + for (int n = 0; n != -1; n = in.read(bytes)) { + fout.write(bytes, 0, n); + } + + } catch (IOException ioe) { + // We might get an IOException trying to overwrite an existing + // jline.dll file if there is another process using the DLL. + // If this happens, ignore errors. + if (!exists) { + throw ioe; + } + } finally { + if (fout != null) { + try { + fout.close(); + } catch (IOException ioe) { + // ignore + } + } + } + + // try to clean up the DLL after the JVM exits + f.deleteOnExit(); + + // now actually load the DLL + System.load(f.getAbsolutePath()); + } + + public int readVirtualKey(InputStream in) throws IOException { + int indicator = readCharacter(in); + + // in Windows terminals, arrow keys are represented by + // a sequence of 2 characters. E.g., the up arrow + // key yields 224, 72 + if (indicator == SPECIAL_KEY_INDICATOR + || indicator == NUMPAD_KEY_INDICATOR) { + int key = readCharacter(in); + + switch (key) { + case UP_ARROW_KEY: + return CTRL_P; // translate UP -> CTRL-P + case LEFT_ARROW_KEY: + return CTRL_B; // translate LEFT -> CTRL-B + case RIGHT_ARROW_KEY: + return CTRL_F; // translate RIGHT -> CTRL-F + case DOWN_ARROW_KEY: + return CTRL_N; // translate DOWN -> CTRL-N + case DELETE_KEY: + return CTRL_QM; // translate DELETE -> CTRL-? + case HOME_KEY: + return CTRL_A; + case END_KEY: + return CTRL_E; + case PAGE_UP_KEY: + return CTRL_K; + case PAGE_DOWN_KEY: + return CTRL_L; + case ESCAPE_KEY: + return CTRL_OB; // translate ESCAPE -> CTRL-[ + case INSERT_KEY: + return CTRL_C; + default: + return 0; + } + } else if (indicator > 128) { + // handle unicode characters longer than 2 bytes, + // thanks to Marc.Herbert@continuent.com + replayStream.setInput(indicator, in); + // replayReader = new InputStreamReader(replayStream, encoding); + indicator = replayReader.read(); + + } + + return indicator; + + } + + public boolean isSupported() { + return true; + } + + /** + * Windows doesn't support ANSI codes by default; disable them. + */ + public boolean isANSISupported() { + return false; + } + + public boolean getEcho() { + return false; + } + + /** + * Unsupported; return the default. + * + * @see Terminal#getTerminalWidth + */ + public int getTerminalWidth() { + return getWindowsTerminalWidth(); + } + + /** + * Unsupported; return the default. + * + * @see Terminal#getTerminalHeight + */ + public int getTerminalHeight() { + return getWindowsTerminalHeight(); + } + + /** + * No-op for exceptions we want to silently consume. + */ + private void consumeException(final Throwable e) { + } + + /** + * Whether or not to allow the use of the JNI console interaction. + */ + public void setDirectConsole(Boolean directConsole) { + this.directConsole = directConsole; + } + + /** + * Whether or not to allow the use of the JNI console interaction. + */ + public Boolean getDirectConsole() { + return this.directConsole; + } + + public synchronized boolean isEchoEnabled() { + return echoEnabled; + } + + public synchronized void enableEcho() { + // Must set these four modes at the same time to make it work fine. + setConsoleMode(getConsoleMode() | ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT + | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT); + echoEnabled = true; + } + + public synchronized void disableEcho() { + // Must set these four modes at the same time to make it work fine. + setConsoleMode(getConsoleMode() + & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT + | ENABLE_PROCESSED_INPUT | ENABLE_WINDOW_INPUT)); + echoEnabled = true; + } + + public InputStream getDefaultBindings() { + return WindowsTerminal.class.getResourceAsStream("windowsbindings.properties"); + } + + /** + * This is awkward and inefficient, but probably the minimal way to add + * UTF-8 support to JLine + * + * @author Marc Herbert + */ + static class ReplayPrefixOneCharInputStream extends InputStream { + byte firstByte; + int byteLength; + InputStream wrappedStream; + int byteRead; + + final String encoding; + + public ReplayPrefixOneCharInputStream(String encoding) { + this.encoding = encoding; + } + + public void setInput(int recorded, InputStream wrapped) throws IOException { + this.byteRead = 0; + this.firstByte = (byte) recorded; + this.wrappedStream = wrapped; + + byteLength = 1; + if (encoding.equalsIgnoreCase("UTF-8")) + setInputUTF8(recorded, wrapped); + else if (encoding.equalsIgnoreCase("UTF-16")) + byteLength = 2; + else if (encoding.equalsIgnoreCase("UTF-32")) + byteLength = 4; + } + + + public void setInputUTF8(int recorded, InputStream wrapped) throws IOException { + // 110yyyyy 10zzzzzz + if ((firstByte & (byte) 0xE0) == (byte) 0xC0) + this.byteLength = 2; + // 1110xxxx 10yyyyyy 10zzzzzz + else if ((firstByte & (byte) 0xF0) == (byte) 0xE0) + this.byteLength = 3; + // 11110www 10xxxxxx 10yyyyyy 10zzzzzz + else if ((firstByte & (byte) 0xF8) == (byte) 0xF0) + this.byteLength = 4; + else + throw new IOException("invalid UTF-8 first byte: " + firstByte); + } + + public int read() throws IOException { + if (available() == 0) + return -1; + + byteRead++; + + if (byteRead == 1) + return firstByte; + + return wrappedStream.read(); + } + + /** + * InputStreamReader is greedy and will try to read bytes in advance. We + * do NOT want this to happen since we use a temporary/"losing bytes" + * InputStreamReader above, that's why we hide the real + * wrappedStream.available() here. + */ + public int available() { + return byteLength - byteRead; + } + } + +} \ No newline at end of file diff --git a/grails-bootstrap/src/main/groovy/org/codehaus/groovy/grails/resolve/GrailsCoreDependencies.java b/grails-bootstrap/src/main/groovy/org/codehaus/groovy/grails/resolve/GrailsCoreDependencies.java index e80ede1084d..d188353d743 100644 --- a/grails-bootstrap/src/main/groovy/org/codehaus/groovy/grails/resolve/GrailsCoreDependencies.java +++ b/grails-bootstrap/src/main/groovy/org/codehaus/groovy/grails/resolve/GrailsCoreDependencies.java @@ -102,6 +102,7 @@ public Object doCall() { ModuleRevisionId.newInstance("org.apache.ant", "ant-trax", "1.7.1"), ModuleRevisionId.newInstance("jline", "jline", "0.9.94"), ModuleRevisionId.newInstance("org.fusesource.jansi", "jansi", "1.2.1"), + ModuleRevisionId.newInstance("net.java.dev.jna", "jna", "3.2.3"), ModuleRevisionId.newInstance("xalan","serializer", "2.7.1"), ModuleRevisionId.newInstance("org.grails", "grails-docs", grailsVersion), ModuleRevisionId.newInstance("org.grails", "grails-bootstrap", grailsVersion), diff --git a/grails-resources/src/grails/home/conf/groovy-starter.conf b/grails-resources/src/grails/home/conf/groovy-starter.conf index 7b5a032c99b..cfc3b331448 100644 --- a/grails-resources/src/grails/home/conf/groovy-starter.conf +++ b/grails-resources/src/grails/home/conf/groovy-starter.conf @@ -12,6 +12,7 @@ load ${grails.home}/dist/grails-bootstrap-@grails.version@.jar load ${grails.home}/lib/jline/jline/jars/jline-@jline.version@.jar load ${grails.home}/lib/org.fusesource.jansi/jansi/jars/jansi-@jansi.version@.jar + load ${grails.home}/lib/net.java.dev.jna/jna/jars/jna-@jna.version@.jar load ${grails.home}/lib/commons-cli/commons-cli/jars/commons-cli-@commons.cli.version@.jar load ${grails.home}/lib/org.codehaus.groovy/groovy-all/jars/groovy-all-@groovy.version@.jar load ${grails.home}/lib/org.apache.ivy/ivy/jars/ivy-@ivy.version@.jar diff --git a/grails-test-suite-uber/src/test/groovy/org/codehaus/groovy/grails/resolve/IvyDependencyManagerTests.groovy b/grails-test-suite-uber/src/test/groovy/org/codehaus/groovy/grails/resolve/IvyDependencyManagerTests.groovy index c6c3860fbb5..99c56170720 100644 --- a/grails-test-suite-uber/src/test/groovy/org/codehaus/groovy/grails/resolve/IvyDependencyManagerTests.groovy +++ b/grails-test-suite-uber/src/test/groovy/org/codehaus/groovy/grails/resolve/IvyDependencyManagerTests.groovy @@ -580,7 +580,7 @@ class IvyDependencyManagerTests extends GroovyTestCase { assertEquals 53, manager.dependencyDescriptors.findAll { it.scope == 'compile'}.size() assertEquals 14, manager.dependencyDescriptors.findAll { it.scope == 'runtime'}.size() assertEquals 4, manager.dependencyDescriptors.findAll { it.scope == 'test'}.size() - assertEquals 22, manager.dependencyDescriptors.findAll { it.scope == 'build'}.size() + assertEquals 23, manager.dependencyDescriptors.findAll { it.scope == 'build'}.size() assertEquals 3, manager.dependencyDescriptors.findAll { it.scope == 'provided'}.size() assertEquals 3, manager.dependencyDescriptors.findAll { it.scope == 'docs'}.size() } @@ -601,7 +601,7 @@ class IvyDependencyManagerTests extends GroovyTestCase { assertEquals 0, manager.dependencyDescriptors.findAll { it.scope == 'compile'}.size() assertEquals 0, manager.dependencyDescriptors.findAll { it.scope == 'runtime'}.size() assertEquals 4, manager.dependencyDescriptors.findAll { it.scope == 'test'}.size() - assertEquals 22, manager.dependencyDescriptors.findAll { it.scope == 'build'}.size() + assertEquals 23, manager.dependencyDescriptors.findAll { it.scope == 'build'}.size() assertEquals 70, manager.dependencyDescriptors.findAll { it.scope == 'provided'}.size() assertEquals 3, manager.dependencyDescriptors.findAll { it.scope == 'docs'}.size() @@ -616,7 +616,7 @@ class IvyDependencyManagerTests extends GroovyTestCase { assertEquals 53, manager.dependencyDescriptors.findAll { it.scope == 'compile'}.size() assertEquals 14, manager.dependencyDescriptors.findAll { it.scope == 'runtime'}.size() assertEquals 4, manager.dependencyDescriptors.findAll { it.scope == 'test'}.size() - assertEquals 22, manager.dependencyDescriptors.findAll { it.scope == 'build'}.size() + assertEquals 23, manager.dependencyDescriptors.findAll { it.scope == 'build'}.size() assertEquals 3, manager.dependencyDescriptors.findAll { it.scope == 'provided'}.size() assertEquals 3, manager.dependencyDescriptors.findAll { it.scope == 'docs'}.size() }