Gradle:使用项目类路径执行Groovy交互式shell

10

我有一个Gradle项目,由几个子项目组成。我刚刚创建了一个新项目,以添加对交互式Groovy shell的支持,我希望可以运行:

gradle console

或者

gradle console:run

所以我的新的console模块的build.gradle文件如下:
apply plugin: 'groovy'
apply plugin:'application'

mainClassName = 'org.codehaus.groovy.tools.shell.Main'

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.2.2'
  compile 'org.fusesource.jansi:jansi:1.11'
  compile 'commons-cli:commons-cli:1.2'
  compile 'jline:jline:2.11'
  compile project(':my-module')
}

task(console, dependsOn: 'classes', type: JavaExec) {
  main = 'org.codehaus.groovy.tools.shell.Main'
  classpath = sourceSets.main.runtimeClasspath
}

然而,当我运行 gradle :console:rungradle console 命令时,会出现以下结果:

:console:run
Groovy Shell (2.2.2, JVM: 1.6.0_45)
Type 'help' or '\h' for help.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
groovy:000> 
BUILD SUCCESSFUL

Total time: 4.529 secs
giovanni@mylaptop:~/Projects/my-project$

因此,交互式 shell 似乎启动了,但立即退出。我做错了什么吗?

编辑: 在 build.gradle 文件中添加了以下内容:

run.standardInput = System.in

现在标准输入从输入流中读取(感谢评论)。

然而,Gradle似乎在这里卡住了:

Groovy Shell (2.2.2, JVM: 1.6.0_45)
Type 'help' or '\h' for help.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
groovy:000> 
> Building 88% > :console:run

无论输入什么都不能被接受。甚至这样也会导致同样的结果:

gradle --no-daemon console:run

更新于2018年:

Dylons所提供的答案似乎已经不再适用,./gradlew console会立即退出:

$ ./gradlew console

配置项目: Task.leftShift(Closure)方法已被弃用,计划在Gradle 5.0中删除。请改用Task.doLast(Action)。 在build_8qb2gvs00xed46ejq1p63fo92.run(/home/jhe052/eclipse-workspace/QuinCe/build.gradle:118)处。 (使用--stacktrace运行以获取此弃用警告的完整堆栈跟踪。)

BUILD SUCCESSFUL in 3s 3个可执行任务:1个已执行,2个最新的

使用doLast替换leftShift(<<)可以消除已弃用的消息,但结果相同。版本信息:

$ ./gradlew  --version

Gradle 4.4.1

构建时间:2017年12月20日15:45:23 UTC 版本号:10ed9dc355dc39f6307cc98fbd8cea314bdd381c

Groovy版本号:2.4.12 Ant版本号:Apache Ant(TM) 1.9.9,编译于2017年2月2日 JVM版本号:1.8.0_151 (Oracle Corporation 25.151-b12) 操作系统:Linux 4.13.0-32-generic amd64


我该如何调试它?我正在使用命令行运行。如果你是正确的,一定有一个Gradle选项可以不将EOF发送到控制台。 - Giovanni Botta
不过,在此之前,我不知道应用程序插件是否有可能进行分叉?另一个“凭空想象”的假设是,由于您没有进行分叉,因此使用的JVM是运行gradle的JVM,并且gradle在启动时关闭stdin。 - fge
还差一点,看看我的编辑。 - Giovanni Botta
听起来很糟糕。:( - Giovanni Botta
这只是你启动JVM的选项 :p - fge
显示剩余6条评论
3个回答

7
这适用于JDK 7+(对于JDK 6,请看下一张图片):
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()}")
    }
}

为了使其在JDK 6中工作,我修改了来自https://dev59.com/d1LTa4cB1Zd3GeqPXylV#4274535的解决方案。我的解决方案适用于标准Linux终端,因此如果您使用的shell使用除'\n'以外的字符序列进行换行或者将退格键编码为127以外的值,则可能需要对其进行一些修改。我没有确定如何使颜色正确打印,因此其输出相当单调,但是它可以完成工作:
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()}")
    }
}

然后,通过以下方式执行:

gradle console

或者,如果您从Gradle获取了很多噪音:

gradle console -q

很不幸,我正在使用JDK 6,因此ProcessBuilder类有很大的不同,上述代码无法工作。 - Giovanni Botta
最好升级到JDK 7或8,因为Oracle不再支持JDK 6:http://www.oracle.com/technetwork/java/eol-135779.html同时,您可以尝试使用ProcessBuilder API进行操作,例如设置redirectErrorStream(true)。我稍后会尝试操作它,并尝试为JDK 6提供解决方案。 - Dylon
向信徒宣讲。我正在处理一个遗留应用程序。 - Giovanni Botta
我在我的原始回复中添加了一个适用于JDK 6的解决方案。 - Dylon
几年后:对我来说,这种方法不起作用(使用java 1.8)。运行“./gradlew console”在成功构建后立即退出。有人知道为什么吗?Gradle 4.4.1,Groovy 2.4.12,Java 1.8.0_151在Ubuntu 16.04上。 - jonasfh
小修正:关于退格键和127。根据标准ASCII表:退格键是ASCII 8。Del是ASCII 127。 - user3624334

2
我创建了一个Gradle插件,允许这样做(https://github.com/tkruse/gradle-groovysh-plugin)。不幸的是,Gradle没有为支持REPL提供有用的IO API / 合同,因此该插件无法与较新版本的Gradle一起使用。
但是,在GitHub上的文档中,您可以找到一个手动解决方案而无需插件
该解决方案的概要如下:
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);
  }
}

然后使用JavaExec任务来运行此代码。
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 = []
}

谢谢!getProperty('org.gradle.daemon')失败了,但是当我删除了doFirst块时,它有点起作用了。然而,有一些Gradle输出干扰了shell输入,所以它并不是非常有用。我会尝试再调整一下... - jonasfh
它从未完美地工作过,gradle没有IO API,并且它们在版本之间更改IO行为。这种方式可以使Gradle输出看起来非常酷,但它破坏了REPL用例。可能您可以将自定义的“ShellMain”类转换为实际应用程序,而无需使用gradle启动(也可从IDE启动)或由gradle构建。 - tkruse
使用 ./gradlew --console plain -q --no-daemon shell 启动 Gradle 更好,现在已经足够好了。感谢您的帮助! - jonasfh

0

Gradle目前不能很好地处理ncurses风格的控制台应用程序。请在单独的控制台窗口中运行groovysh或者运行groovyConsole代替。


是的,我想最终我会使用 groovysh。我不喜欢 groovyConsole,因为它不是交互式的。 - Giovanni Botta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接