提交 a2691a81 编写于 作者: L Love Leifland 提交者: Love Kristofer Leifland

Print user friendly error when starting Cypher Shell in unsupported jvm

Build Cypher Shell as a multi release jar. Starting with an old jvm version
prints a more user friendly error message, instead of failing with the cryptic
`Unrecognized option: --add-opens`.
上级 270362ab
......@@ -157,6 +157,49 @@
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<!-- The purpose of this execution is to provide a user-friendly error message when started with an unsupported jvm -->
<execution>
<id>compile-java7</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<source>1.7</source>
<target>1.7</target>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java7</compileSourceRoot>
</compileSourceRoots>
</configuration>
</execution>
<!-- This is the normal execution -->
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>${maven.compiler.target}</release>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>appassembler-maven-plugin</artifactId>
......@@ -165,14 +208,9 @@
<assembleDirectory>${project.build.directory}/assembly</assembleDirectory>
<repositoryLayout>flat</repositoryLayout>
<repositoryName>lib</repositoryName>
<extraJvmArguments>
--add-opens java.base/java.net=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
</extraJvmArguments>
<programs>
<program>
<mainClass>${name-of-main-class}</mainClass>
<mainClass>org.neo4j.shell.startup.CypherShellBoot</mainClass>
<id>cypher-shell</id>
</program>
</programs>
......
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.shell.startup;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.neo4j.shell.Main;
public class CypherShellBoot {
/**
* IMPORTANT NOTE!
* The only purpose of this class is just to forward to the actual main. Its part of a multi-version jar
* to be able to provide a useful error message when used on an old and unsupported version of java.
*/
public static void main(String[] args) throws IOException, InterruptedException {
jvmCheck();
final var processBuilder = new ProcessBuilder(command(args));
processBuilder.inheritIO();
processBuilder.environment().putAll(environment());
Process process = null;
int exitCode;
try {
process = processBuilder.start();
exitCode = process.waitFor();
} catch (Exception e) {
if (process != null) {
process.destroy();
}
throw e;
}
System.exit(exitCode);
}
private static void jvmCheck() {
if (Runtime.version().feature() < 17) {
System.err.println(
"You are using an unsupported version of the Java runtime. Please use Oracle(R) Java(TM) 17 or OpenJDK(TM) 17.");
}
}
private static List<String> command(String[] args) {
final String javaCommand = ProcessHandle.current()
.info()
.command()
.orElseThrow(() -> new IllegalStateException("Wasn't able to figure out java binary"));
// The `app.home` property is set by the appassembler-maven-plugin
final String classPath = System.getProperty("app.home") + File.separator + "lib" + File.separator + "*";
final var command = new ArrayList<String>();
command.add(javaCommand);
command.addAll(jvmInputArguments());
command.add("-cp");
command.add(classPath);
command.add(Main.class.getName());
command.addAll(Arrays.asList(args));
return command;
}
private static Map<String, String> environment() {
if (System.getenv() == null) {
return Map.of();
}
return System.getenv();
}
private static List<String> jvmInputArguments() {
final var arguments = new ArrayList<String>();
arguments.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
arguments.addAll(extraJvmArguments());
return arguments;
}
private static List<String> extraJvmArguments() {
return List.of(
"--add-opens", "java.base/java.net=ALL-UNNAMED",
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
"--add-opens", "java.base/java.nio=ALL-UNNAMED");
}
}
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.shell.startup;
public class CypherShellBoot {
/**
* IMPORTANT NOTE!
* This class is compiled using Java 7 and can not use any dependencies or include any other classes.
* Its only purpose is to print a useful error message when Cypher Shell is started using an old, unsupported java.
*/
public static void main(String[] args) {
printJavaVersionErrorMessage();
}
static void printJavaVersionErrorMessage() {
String version = System.getProperty("java.version");
System.out.println("Unsupported Java " + version
+ " detected. Please use Oracle(R) Java(TM) 17, OpenJDK(TM) 17 to run Cypher Shell.");
}
}
......@@ -76,6 +76,16 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
......
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.shell;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.io.IOUtils;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.MutableList;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
public class BinaryNonInteractiveIntegrationTest {
private static final String USER = "neo4j";
private static final String PASSWORD = "neo";
@TempDir
Path tempDir;
private File cypherShellBinary = cypherShellBinary();
@Test
void simpleReturn() {
assertOutput(defaultArgsWith(), "return 1 as result;", equalTo("result\n1\n"));
}
@Test
void simpleReturnVerbose() {
assertOutput(
defaultArgsWith("--format", "verbose"),
"return 1 as result;",
startsWith(
"""
+--------+
| result |
+--------+
| 1 |
+--------+
1 row
ready to start consuming query after"""));
}
private static MutableList<String> defaultArgsWith(String... args) {
return Lists.mutable
.with("-u", USER, "-p", PASSWORD, "--non-interactive")
.withAll(Arrays.asList(args));
}
private void assertOutput(List<String> args, String input, Matcher<String> expectedOutput) {
final var command =
Lists.mutable.with(cypherShellBinary.getAbsolutePath()).withAll(args);
final var result = execute(command, input);
assertEquals(0, result.exitCode(), failureMessage(result));
assertThat(result.out(), expectedOutput);
assertEquals("", result.err());
}
private Supplier<String> failureMessage(ProcessExecution result) {
return () -> "\nFailure executing: " + String.join(" ", result.command()) + "\n" + "Exit code:"
+ result.exitCode() + "\n" + "Output:\n"
+ result.out()
+ "\n\n" + "Error output:\n"
+ result.err()
+ "\n\n";
}
private ProcessExecution execute(List<String> command, String input) {
Path inFile = tempDir.resolve("input.txt");
Process process = null;
try {
Files.write(inFile, input.getBytes(), StandardOpenOption.CREATE_NEW);
process = new ProcessBuilder(command).redirectInput(inFile.toFile()).start();
final var out = IOUtils.toString(process.getInputStream(), Charset.defaultCharset());
final var err = IOUtils.toString(process.getErrorStream(), Charset.defaultCharset());
int exitCode = process.waitFor();
return new ProcessExecution(command, out, err, exitCode);
} catch (Exception e) {
if (process != null) {
process.destroy();
}
throw new RuntimeException("Failed to run: " + String.join(" ", command), e);
}
}
public static File cypherShellBinary() {
// TODO Test using the binary extracted from the zip distribution instead
// TODO Also test native binaries
if (System.getProperty("os.name").startsWith("Windows")) {
return new File("../cypher-shell/target/assembly/bin/cypher-shell.bat");
}
return new File("../cypher-shell/target/assembly/bin/cypher-shell");
}
}
record ProcessExecution(List<String> command, String out, String err, int exitCode) {}
......@@ -69,6 +69,7 @@ import org.neo4j.shell.test.AssertableMain;
import org.neo4j.shell.util.Version;
import org.neo4j.shell.util.Versions;
// NOTE! Consider adding tests to BinaryNonInteractiveIntegrationTest instead of here.
@Timeout(value = 5, unit = MINUTES)
class MainIntegrationTest {
private static final String USER = "neo4j";
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册