Gradle: execute Groovy interactive shell with project classpath

后端 未结 3 827
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-04 21:38

I have a Gradle project composed of several sub projects. I just created a new one to add support for an interactive Groovy shell that I would like to run with:



        
相关标签:
3条回答
  • 2021-01-04 22:28

    Gradle doesn't currently handle ncurses style console applications well. Either run groovysh in a separate console, or run groovyConsole instead.

    0 讨论(0)
  • 2021-01-04 22:31

    This works for JDK 7+ (for JDK 6, look at the next figure):

    configurations {
        console
    }
    
    dependencies {
        // ... compile dependencies, runtime dependencies, etc.
        console 'commons-cli:commons-cli:1.2'
        console('jline:jline:2.11') {
            exclude(group: 'junit', module: 'junit')
        }
        console 'org.codehaus.groovy:groovy-groovysh:2.2.+'
    }
    
    task(console, dependsOn: 'classes') << {
        def classpath = sourceSets.main.runtimeClasspath + configurations.console
    
        def command = [
            'java',
            '-cp', classpath.collect().join(System.getProperty('path.separator')),
            'org.codehaus.groovy.tools.shell.Main',
            '--color'
        ]
    
        def proc = new ProcessBuilder(command)
            .redirectOutput(ProcessBuilder.Redirect.INHERIT)
            .redirectInput(ProcessBuilder.Redirect.INHERIT)
            .redirectError(ProcessBuilder.Redirect.INHERIT)
            .start()
    
        proc.waitFor()
    
        if (0 != proc.exitValue()) {
            throw new RuntimeException("console exited with status: ${proc.exitValue()}")
        }
    }
    

    To make this work for JDK 6, I modified the solution from https://stackoverflow.com/a/4274535/206543. My solution is tailored to a standard Linux terminal, so if you are running a shell that uses a char sequence other than '\n' for newlines or that encodes backspaces as a value other other 127, you may need to modify it some. I didn't determine how to make colors print correctly, so its output is rather monotone, but it will get the job done:

    configurations {
        console
    }
    
    dependencies {
        // ... compile dependencies, runtime dependencies, etc.
        console 'commons-cli:commons-cli:1.2'
        console('jline:jline:2.11') {
            exclude(group: 'junit', module: 'junit')
        }
        console 'org.codehaus.groovy:groovy-groovysh:2.2.+'
    }
    
    class StreamCopier implements Runnable {
        def istream
        def ostream
        StreamCopier(istream, ostream) {
            this.istream = istream
            this.ostream = ostream
        }
        void run() {
            int n
            def buffer = new byte[4096]
            while ((n = istream.read(buffer)) != -1) {
                ostream.write(buffer, 0, n)
                ostream.flush()
            }
        }
    }
    
    class InputCopier implements Runnable {
        def istream
        def ostream
        def stdout
        InputCopier(istream, ostream, stdout) {
            this.istream = istream
            this.ostream = ostream
            this.stdout = stdout
        }
        void run() {
            try {
                int n
                def buffer = java.nio.ByteBuffer.allocate(4096)
                while ((n = istream.read(buffer)) != -1) {
                    ostream.write(buffer.array(), 0, n)
                    ostream.flush()
                    buffer.clear()
                    if (127 == buffer.get(0)) {
                        stdout.print("\b \b")
                        stdout.flush()
                    }
                }
            }
            catch (final java.nio.channels.AsynchronousCloseException exception) {
                // Ctrl+D pressed
            }
            finally {
                ostream.close()
            }
        }
    }
    
    def getChannel(istream) {
        def f = java.io.FilterInputStream.class.getDeclaredField("in")
        f.setAccessible(true)
        while (istream instanceof java.io.FilterInputStream) {
            istream = f.get(istream)
        }
        istream.getChannel()
    }
    
    task(console, dependsOn: 'classes') << {
        def classpath = sourceSets.main.runtimeClasspath + configurations.console
    
        def command = [
            'java',
            '-cp', classpath.collect().join(System.getProperty('path.separator')),
            'org.codehaus.groovy.tools.shell.Main'
        ]
    
        def proc = new ProcessBuilder(command).start()
    
        def stdout = new Thread(new StreamCopier(proc.getInputStream(), System.out))
        stdout.start()
    
        def stderr = new Thread(new StreamCopier(proc.getErrorStream(), System.err))
        stderr.start()
    
        def stdin  = new Thread(new InputCopier(
            getChannel(System.in),
            proc.getOutputStream(),
            System.out))
        stdin.start()
    
        proc.waitFor()
        System.in.close()
        stdout.join()
        stderr.join()
        stdin.join()
    
        if (0 != proc.exitValue()) {
            throw new RuntimeException("console exited with status: ${proc.exitValue()}")
        }
    }
    

    Then, execute it via:

    gradle console
    

    or, if you get a lot of noise from gradle:

    gradle console -q
    
    0 讨论(0)
  • 2021-01-04 22:33

    I created a gradle plugin that allowed this (https://github.com/tkruse/gradle-groovysh-plugin). Sadly gradle does not provide a useful API / contract for IO that supports a REPL, so the plugin does not work with newer versions of Gradle.

    However, in the docs on github you can find a manual solution without a plugin.

    The outline of that solution is:

    package shell;
    
    import org.codehaus.groovy.tools.shell.Main;
    import org.fusesource.jansi.AnsiConsole;
    
    class ShellMain {
      public static void main(String[] args) {
        // workaround for jAnsi problems, (backspace and arrow keys not working)
        AnsiConsole.systemUninstall();
        Main.main(args);
      }
    }
    

    Then Use a JavaExec task to run this

    apply plugin: 'java'
    
    dependencies {
      compile('commons-cli:commons-cli:1.2')
      compile('org.codehaus.groovy:groovy-all:2.4.4') { force = true }
      // when using groovy < 2.2 above: "jline:jline:1.0"
      compile("jline:jline:2.11") {
        exclude(group: 'junit', module: 'junit')
      }
    }
    
    task shell(dependsOn: 'testClasses', type: JavaExec) {
      doFirst {
        if (getProperty('org.gradle.daemon') == 'true') {
            throw new IllegalStateException('Do not run shell with gradle daemon, it will eat your arrow keys.')
        }
      }
      standardInput = System.in
      main = 'shell.ShellMain'
      classpath = sourceSets.main.runtimeClasspath
      jvmArgs = []
    }
    
    0 讨论(0)
提交回复
热议问题