Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class SbomPlugin implements Plugin<Project> {
private static Map<String, String> LICENSE_MAPPING = [
'pkg:maven/org.antlr/antlr4-runtime@4.7.2?type=jar' : 'BSD-3-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/jline/jline@2.14.6?type=jar' : 'BSD-2-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/org.jline/jline@3.23.0?type=jar' : 'BSD-2-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/org.jline/jline@3.23.0?type=jar' : 'BSD-3-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/org.jline/jline@3.30.6?type=jar' : 'BSD-3-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/org.liquibase.ext/liquibase-hibernate5@4.27.0?type=jar': 'Apache-2.0', // maps incorrectly because of https://github.com/liquibase/liquibase/issues/2445 & the base pom does not define a license
'pkg:maven/com.oracle.coherence.ce/coherence-bom@25.03.1?type=pom': 'UPL-1.0', // does not have map based on license id
'pkg:maven/com.oracle.coherence.ce/coherence-bom@22.06.2?type=pom': 'UPL-1.0', // does not have map based on license id
Expand Down
10 changes: 7 additions & 3 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ ext {
'directory-watcher.version' : '0.19.1',
'gradle-spock.version' : '2.3-groovy-3.0',
'grails-publish-plugin.version' : '0.0.4',
'jansi.version' : '1.18',
'jansi.version' : '2.4.2',
'javaparser-core.version' : '3.27.0',
'jline.version' : '2.14.6',
'jline.version' : '3.30.6',
// TODO: Remove jline2 when upgrading to Groovy 5 (groovy-groovysh 5.x uses JLine 3)
'jline2.version' : '2.14.6',
'jna.version' : '5.17.0',
'jquery.version' : '3.7.1',
'objenesis.version' : '3.4',
Expand All @@ -59,7 +61,9 @@ ext {
'grails-publish-plugin' : "org.apache.grails.gradle:grails-publish:${gradleBomDependencyVersions['grails-publish-plugin.version']}",
'jansi' : "org.fusesource.jansi:jansi:${gradleBomDependencyVersions['jansi.version']}",
'javaparser-core' : "com.github.javaparser:javaparser-core:${gradleBomDependencyVersions['javaparser-core.version']}",
'jline' : "jline:jline:${gradleBomDependencyVersions['jline.version']}",
'jline' : "org.jline:jline:${gradleBomDependencyVersions['jline.version']}",
// TODO: Remove jline2 when upgrading to Groovy 5 (groovy-groovysh 5.x uses JLine 3)
'jline2' : "jline:jline:${gradleBomDependencyVersions['jline2.version']}",
'jna' : "net.java.dev.jna:jna:${gradleBomDependencyVersions['jna.version']}",
'objenesis' : "org.objenesis:objenesis:${gradleBomDependencyVersions['objenesis.version']}",
'spring-boot-cli' : "org.springframework.boot:spring-boot-cli:${gradleBomDependencyVersions['spring-boot.version']}",
Expand Down
1 change: 1 addition & 0 deletions gradle/docs-dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ configurations.register('documentation') {
dependencies {
add('documentation', platform(project(':grails-bom')))
add('documentation', 'org.fusesource.jansi:jansi')
// TODO: Remove jline:jline (JLine 2) when upgrading to Groovy 5 (groovy-groovysh 5.x uses JLine 3)
add('documentation', 'jline:jline')
add('documentation', 'com.github.javaparser:javaparser-core')
add('documentation', 'org.apache.groovy:groovy')
Expand Down
4 changes: 2 additions & 2 deletions grails-bootstrap/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ dependencies {

compileOnly 'io.methvin:directory-watcher'
compileOnly 'org.fusesource.jansi:jansi'
compileOnly 'jline:jline'
compileOnly 'org.jline:jline'
compileOnly 'net.java.dev.jna:jna'

api 'org.yaml:snakeyaml'

testImplementation 'org.apache.groovy:groovy-xml'
testImplementation 'org.apache.groovy:groovy-templates'
testImplementation 'org.fusesource.jansi:jansi'
testImplementation 'jline:jline'
testImplementation 'org.jline:jline'

testImplementation 'org.apache.groovy:groovy-test-junit5'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
Expand Down
189 changes: 108 additions & 81 deletions grails-bootstrap/src/main/groovy/grails/build/logging/GrailsConsole.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,39 +18,34 @@
*/
package grails.build.logging;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.List;
import java.util.Stack;

import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.StackTraceUtils;
import org.codehaus.groovy.runtime.typehandling.NumberMath;

import jline.Terminal;
import jline.TerminalFactory;
import jline.UnixTerminal;
import jline.console.ConsoleReader;
import jline.console.completer.Completer;
import jline.console.history.FileHistory;
import jline.console.history.History;
import jline.internal.Log;
import jline.internal.ShutdownHooks;
import jline.internal.TerminalLineSettings;
import org.apache.tools.ant.BuildException;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Color;
import org.fusesource.jansi.AnsiConsole;
import org.jline.reader.Completer;
import org.jline.reader.History;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.LineReaderImpl;
import org.jline.reader.impl.completer.AggregateCompleter;
import org.jline.reader.impl.history.DefaultHistory;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;

import grails.util.Environment;
import org.grails.build.interactive.CandidateListCompletionHandler;
import org.grails.build.logging.GrailsConsoleErrorPrintStream;
import org.grails.build.logging.GrailsConsolePrintStream;

Expand Down Expand Up @@ -114,7 +109,7 @@ public class GrailsConsole implements ConsoleLogger {
/**
* The reader to read info from the console
*/
ConsoleReader reader;
LineReader reader;

Terminal terminal;

Expand All @@ -123,6 +118,11 @@ public class GrailsConsole implements ConsoleLogger {

History history;

/**
* List of completers to be aggregated for tab completion
*/
private final List<Completer> completers = new java.util.ArrayList<>();

/**
* The category of the current output
*/
Expand Down Expand Up @@ -179,8 +179,8 @@ protected GrailsConsole() throws IOException {
* @throws IOException
*/
public void reinitialize(InputStream systemIn, PrintStream systemOut, PrintStream systemErr) throws IOException {
if (reader != null) {
reader.shutdown();
if (terminal != null) {
terminal.close();
}
initialize(systemIn, systemOut, systemErr);
}
Expand All @@ -190,25 +190,31 @@ protected void initialize(InputStream systemIn, PrintStream systemOut, PrintStre

redirectSystemOutAndErr(true);

System.setProperty(ShutdownHooks.JLINE_SHUTDOWNHOOK, "false");

if (isInteractiveEnabled()) {
reader = createConsoleReader(systemIn);
reader.setBellEnabled(false);
reader.setCompletionHandler(new CandidateListCompletionHandler());
if (isActivateTerminal()) {
terminal = createTerminal();
}

terminal = createTerminal();
history = prepareHistory();
if (history != null) {
reader.setHistory(history);
}
reader = createLineReader(terminal, history);
initializeHistory();
} else if (isActivateTerminal()) {
terminal = createTerminal();
}
}

/**
* Initializes history by attaching it to the reader and loading existing entries.
* This must be called after the LineReader is fully constructed.
*/
private void initializeHistory() {
if (history instanceof DefaultHistory && reader != null) {
DefaultHistory defaultHistory = (DefaultHistory) history;
try {
defaultHistory.attach(reader);
} catch (Exception e) {
// History initialization failed, continue without persistent history
}
}
}

protected void bindSystemOutAndErr(PrintStream systemOut, PrintStream systemErr) {
originalSystemOut = unwrapPrintStream(systemOut);
out = originalSystemOut;
Expand Down Expand Up @@ -251,51 +257,59 @@ private boolean readPropOrTrue(String prop) {
return property == null ? true : Boolean.valueOf(property);
}

protected ConsoleReader createConsoleReader(InputStream systemIn) throws IOException {
// need to swap out the output to avoid logging during init
final PrintStream nullOutput = new PrintStream(new ByteArrayOutputStream());
final PrintStream originalOut = Log.getOutput();
try {
Log.setOutput(nullOutput);
ConsoleReader consoleReader = new ConsoleReader(systemIn, out);
consoleReader.setExpandEvents(false);
return consoleReader;
} finally {
Log.setOutput(originalOut);
protected LineReader createLineReader(Terminal terminal, History history) throws IOException {
LineReaderBuilder builder = LineReaderBuilder.builder()
.terminal(terminal)
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true);
if (history != null) {
builder.variable(LineReader.HISTORY_FILE, new File(System.getProperty("user.home"), HISTORYFILE).toPath());
builder.history(history);
}
return builder.build();
}

/**
* Creates the instance of Terminal used directly in GrailsConsole. Note that there is also
* another terminal instance created implicitly inside of ConsoleReader. That instance
* is controlled by the jline.terminal system property.
* Creates the instance of Terminal used directly in GrailsConsole.
*/
protected Terminal createTerminal() {
terminal = TerminalFactory.create();
if (isWindows()) {
terminal.setEchoEnabled(true);
}
protected Terminal createTerminal() throws IOException {
Terminal terminal = TerminalBuilder.builder()
.system(true)
.build();
return terminal;
}

public void resetCompleters() {
final ConsoleReader reader = getReader();
if (reader != null) {
Collection<Completer> completers = reader.getCompleters();
for (Completer completer : completers) {
reader.removeCompleter(completer);
}
completers.clear();
updateCompleter();
}

// for some unknown reason / bug in JLine you have to iterate over twice to clear the completers (WTF)
completers = reader.getCompleters();
for (Completer completer : completers) {
reader.removeCompleter(completer);
}
public void addCompleter(Completer completer) {
if (completer != null) {
completers.add(completer);
updateCompleter();
}
}

/**
* Prepares a history file to be used by the ConsoleReader. This file
* Updates the LineReader completer using an AggregateCompleter when needed.
*/
private void updateCompleter() {
if (reader == null) {
return;
}
if (!(reader instanceof LineReaderImpl)) {
return;
}
LineReaderImpl lineReader = (LineReaderImpl) reader;
if (completers.isEmpty()) {
lineReader.setCompleter(null);
} else {
lineReader.setCompleter(new AggregateCompleter(completers));
}
}

/**
* Prepares a history file to be used by the LineReader. This file
* will live in the home directory of the user.
*/
protected History prepareHistory() throws IOException {
Expand All @@ -307,7 +321,7 @@ protected History prepareHistory() throws IOException {
// can't create the file, so no history for you
}
}
return file.canWrite() ? new FileHistory(file) : null;
return file.canWrite() ? new DefaultHistory() : null;
}

public boolean isWindows() {
Expand All @@ -334,8 +348,12 @@ public static synchronized void removeInstance() {
if (instance != null) {
instance.removeShutdownHook();
instance.restoreOriginalSystemOutAndErr();
if (instance.getReader() != null) {
instance.getReader().shutdown();
if (instance.terminal != null) {
try {
instance.terminal.close();
} catch (IOException e) {
// ignore
}
}
instance = null;
}
Expand All @@ -348,24 +366,18 @@ public void beforeShutdown() {

protected void restoreTerminal() {
try {
terminal.restore();
if (terminal != null) {
terminal.close();
}
} catch (Exception e) {
// ignore
}
if (terminal instanceof UnixTerminal) {
// workaround for GRAILS-11494
try {
new TerminalLineSettings().set("sane");
} catch (Exception e) {
// ignore
}
}
}

protected void persistHistory() {
if (history instanceof Flushable) {
if (history != null && reader != null) {
try {
((Flushable) history).flush();
history.save();
} catch (Throwable e) {
// ignore exception
}
Expand Down Expand Up @@ -442,7 +454,7 @@ public boolean isStacktrace() {
*/
public InputStream getInput() {
assertAllowInput();
return reader.getInput();
return terminal != null ? terminal.input() : System.in;
}

private void assertAllowInput() {
Expand Down Expand Up @@ -471,7 +483,7 @@ public void setLastMessage(String lastMessage) {
this.lastMessage = lastMessage;
}

public ConsoleReader getReader() {
public LineReader getReader() {
return reader;
}

Expand Down Expand Up @@ -674,7 +686,7 @@ private void logSimpleError(String msg) {
}

public boolean isAnsiEnabled() {
return Ansi.isEnabled() && (terminal != null && terminal.isAnsiSupported()) && ansiEnabled;
return Ansi.isEnabled() && (terminal != null && !"dumb".equals(terminal.getType())) && ansiEnabled;
}

/**
Expand Down Expand Up @@ -875,10 +887,17 @@ private String readLine(String prompt, boolean secure) {
assertAllowInput(prompt);
userInputActive = true;
try {
Character inputMask = secure ? SECURE_MASK_CHAR : defaultInputMask;
return reader.readLine(prompt, inputMask);
} catch (IOException e) {
throw new RuntimeException("Error reading input: " + e.getMessage());
if (secure) {
return reader.readLine(prompt, SECURE_MASK_CHAR);
} else if (defaultInputMask == null) {
return reader.readLine(prompt);
} else {
return reader.readLine(prompt, defaultInputMask);
}
} catch (org.jline.reader.UserInterruptException e) {
return null;
} catch (org.jline.reader.EndOfFileException e) {
return null;
} finally {
userInputActive = false;
}
Expand Down Expand Up @@ -1041,4 +1060,12 @@ public Character getDefaultInputMask() {
public void setDefaultInputMask(Character defaultInputMask) {
this.defaultInputMask = defaultInputMask;
}

/**
* Gets the history for the LineReader
* @return the history
*/
public History getHistory() {
return history;
}
}
Loading
Loading