Gradle: выполнить Groovy интерактивную оболочку с проектом classpath

У меня есть проект Gradle, состоящий из нескольких подпроектов. Я только что создал новую, чтобы добавить поддержку интерактивной оболочки Groovy, с которой я хотел бы работать:

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:run или же gradle 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$

Таким образом, кажется, что интерактивная оболочка запускается, но сразу же выходит.

Я делаю что-то неправильно?

РЕДАКТИРОВАТЬ: Добавлено следующее в файл 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:

Дилонс принял ответ, похоже, больше не работает, ./gradlew console выходит немедленно:

$ ./gradlew console

Настройка проекта: метод Task.leftShift(Closure) устарел и планируется удалить в Gradle 5.0. Пожалуйста, используйте Task.doLast(Action) вместо этого. at build_8qb2gvs00xed46ejq1p63fo92.run(/home/jhe052/eclipse-workspace/QuinCe/build.gradle:118) (Запустите с --stacktrace, чтобы получить полную трассировку стека этого предупреждения об устаревании.)

СОЗДАЙТЕ УСПЕШНО в 3S 3 действенных задач: 1 выполнено, 2 актуальных

Замена leftShift (<<) на doLast избавляет от устаревшего сообщения, но с тем же результатом. Информация о версии:

$ ./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, скомпилированная 2 февраля 2017 г. JVM: 1.8.0_151 (Oracle Corporation 25.151-b12) ОС: Linux 4.13.0-32-generic amd64

3 ответа

Решение

Это работает для 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, я изменил решение с /questions/24512238/zapustite-podprotsess-predostavte-vhod-i-vyihod-dlya-nego-pravilno-v-java/24512261#24512261. Мое решение предназначено для стандартного терминала Linux, поэтому, если вы используете оболочку, которая использует последовательность символов, отличную от '\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

Я создал плагин Gradle, который позволил это ( https://github.com/tkruse/gradle-groovysh-plugin). К сожалению, gradle не предоставляет полезного API / контракта для ввода-вывода, который поддерживает REPL, поэтому плагин не работает с более новыми версиями 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 = []
}

В настоящее время Gradle плохо обрабатывает консольные приложения в стиле ncurses. Либо беги groovysh в отдельной консоли или запустить groovyConsole вместо.

Другие вопросы по тегам