【问题标题】:Gradle: execute Groovy interactive shell with project classpathGradle:使用项目类路径执行 Groovy 交互式 shell
【发布时间】:2014-04-29 08:32:26
【问题描述】:

我有一个由几个子项目组成的 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

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

但是,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 运行以获取此弃用警告的完整堆栈跟踪。)

在 3 秒内构建成功 3 个可操作的任务:1 个已执行,2 个是最新的

将 leftShift (

$ ./gradlew  --version

Gradle 4.4.1

构建时间:2017-12-20 15:45:23 UTC 修订:10ed9dc355dc39f6307cc98fbd8cea314bdd381c

Groovy:2.4.12 Ant:2017 年 2 月 2 日编译的 Apache Ant(TM) 版本 1.9.9 JVM:1.8.0_151(甲骨文公司 25.151-b12) 操作系统:Linux 4.13.0-32-generic amd64

【问题讨论】:

  • 您是否尝试并调试过看看会发生什么?我怀疑 shell 看到 EOF 这就是它退出的原因
  • 如何调试它?我正在从命令行运行。如果你是对的,那么必须有一个 Gradle 选项来不发送到控制台 EOF。
  • 在此之前,虽然我不知道应用程序插件,但是否有可能分叉?另一个“凭空而来”的假设是,既然你不 fork,那么使用的 JVM 就是运行 gradle 的那个,而 gradle 在启动时会关闭 stdin
  • 这是个好问题。我相信它确实会分叉。
  • 是的,它在 child process 中。

标签: java shell groovy gradle


【解决方案1】:

这适用于 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://stackoverflow.com/a/4274535/206543 的解决方案。我的解决方案是针对标准 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 类有很大不同,上述方法不起作用。
  • 最佳情况下,您应该升级到 JDK 7 或 8,因为 Oracle 不再支持 JDK 6:oracle.com/technetwork/java/eol-135779.html 同时,您可以尝试使用 ProcessBuilder API 以使其工作,比如设置redirectErrorStream(true)。稍后我会尝试一下,并尝试为 JDK 6 提供解决方案。
  • 向合唱团讲道。我正在开发旧版应用程序。
  • 我在原始回复中添加了 JDK 6 的解决方案。
  • ...几年后:对我来说,这种方法行不通(使用 java 1.8)。运行./gradlew console 构建成功后立即退出。有谁知道为什么? Ubuntu 16.04 上的 Gradle 4.4.1、Groovy 2.4.12、Java 1.8.0_151
【解决方案2】:

我创建了一个允许这样做的 gradle 插件 (https://github.com/tkruse/gradle-groovysh-plugin)。遗憾的是,gradle 没有为支持 REPL 的 IO 提供有用的 API/合约,因此该插件不适用于较新版本的 Gradle。

但是,在 github 上的文档中,您可以找到 manual solution without a plugin

该解决方案的大纲是:

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 输入,所以它不是很有用。我会尝试更多地摆弄它......
  • 它从来没有完美地工作过,gradle 没有 IO API,它们会在版本之间改变 IO 行为。 Gradle 输出以这种方式看起来很酷,但它会扼杀 REPL 用例。可能您可以将自定义的“ShellMain”类转换为没有 gradle 启动的实际应用程序(也可以从 IDE 启动)或通过 gradle 构建。
  • ./gradlew --console plain -q --no-daemon shell 开始gradle 效果更好,现在对我来说已经足够好了。感谢您的帮助!
【解决方案3】:

Gradle 目前不能很好地处理 ncurses 样式的控制台应用程序。在单独的控制台中运行 groovysh,或者改为运行 groovyConsole

【讨论】:

  • 是的,我想我最终会做groovysh。我不喜欢groovyConsole,因为它不是交互式的。
猜你喜欢
  • 2017-06-09
  • 1970-01-01
  • 1970-01-01
  • 2021-01-10
  • 1970-01-01
  • 1970-01-01
  • 2017-02-13
  • 2012-11-25
  • 1970-01-01
相关资源
最近更新 更多