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
вместо.