From f87621b43187c95ff0f67b4079eb7e65f1ecc01a Mon Sep 17 00:00:00 2001 From: Max Rydahl Andersen Date: Sun, 29 Dec 2019 00:26:20 +0100 Subject: [PATCH] [patch] enable debug --- build.gradle | 8 ++ readme.adoc | 14 ++- src/main/java/dk/xam/jbang/ExitException.java | 17 +++- src/main/java/dk/xam/jbang/Main.java | 68 ++++++++------ src/main/java/dk/xam/jbang/Util.java | 2 +- .../java/dk/xam/jbang/VersionProvider.java | 10 ++- src/test/java/dk/xam/jbang/TestArguments.java | 79 ++++++++++++----- src/test/java/dk/xam/jbang/TestMain.java | 88 +++++++++++++++++++ 8 files changed, 230 insertions(+), 56 deletions(-) diff --git a/build.gradle b/build.gradle index 59374f297..b42294027 100644 --- a/build.gradle +++ b/build.gradle @@ -83,6 +83,8 @@ jar { } } + + shadowJar { minimize() // warning is printed about deprecation in 7.0 but if I fix this shadowjar turns into a bogus file @@ -93,6 +95,12 @@ test { useJUnitPlatform() } +task copyTestResources(type: Copy) { + from "${projectDir}/examples" + into "${buildDir}/classes/java/test/examples" +} +processTestResources.dependsOn copyTestResources + task createChecksum(type: Checksum) { dependsOn(assembleDist) files = distZip.outputs.files diff --git a/readme.adoc b/readme.adoc index 61970ac16..259393a40 100644 --- a/readme.adoc +++ b/readme.adoc @@ -11,7 +11,7 @@ Then try `jbang` which gives you: * Dependency declarations using `//DEPS ` for automatic dependency resolution * Dependency resolution caching -* (PLANNED) Launch with debug enabled for instant debugging from your favorite IDE +* Launch with debug enabled for instant debugging from your favorite IDE * (PLANNED) Generate gradle/maven file with dependencies for easy editing in your favorite IDE * (PLANNED) Lookup dependencies with a short-hand name, i.e. `// DEPS log4j:1.2+,picocli` for quick getting started. @@ -124,6 +124,18 @@ $ ./classpath_example.java 1 [main] INFO classpath_example - Hello from Java! ``` +## Debugging + +When running `.java` scripts with `jbang` you can pass the `--debug`-flag and the script will enable debug, +suspend the execution and wait until you connect a debugger to port 5005. + +``` +jbang --debug helloworld.java +Listening for transport dt_socket at address: 5005 +``` + +NOTE: Be sure to put a breakpoint in your IDE/debugger before you connect to make the debugger actually stop when you need it. + ## FAQ [qanda] diff --git a/src/main/java/dk/xam/jbang/ExitException.java b/src/main/java/dk/xam/jbang/ExitException.java index 996f60815..afd6e7a2f 100644 --- a/src/main/java/dk/xam/jbang/ExitException.java +++ b/src/main/java/dk/xam/jbang/ExitException.java @@ -1,6 +1,17 @@ package dk.xam.jbang; -public class ExitException extends Throwable { - public ExitException(int status) { - } +/** + * Used when wanting to exit app from a command. + */ +public class ExitException extends RuntimeException { + + private final int status; + + public ExitException(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } } diff --git a/src/main/java/dk/xam/jbang/Main.java b/src/main/java/dk/xam/jbang/Main.java index 9afb964a1..d0db22f18 100644 --- a/src/main/java/dk/xam/jbang/Main.java +++ b/src/main/java/dk/xam/jbang/Main.java @@ -13,13 +13,13 @@ import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; -@Command(name="jbang", mixinStandardHelpOptions = false, versionProvider = VersionProvider.class, description = "Compiles and runs .java/.jsh scripts.") +@Command(name = "jbang", footer = "\nCopyright: 2020 Max Rydahl Andersen, License: MIT\nWebsite: https://github.com/maxandersen/jbang", mixinStandardHelpOptions = false, versionProvider = VersionProvider.class, description = "Compiles and runs .java/.jsh scripts.") public class Main implements Callable { @Spec CommandSpec spec; - @Option(names = {"-d", "--debug"}, arity = "0", description = "Launch with java debug enabled.") + @Option(names = {"-d", "--debug"}, description = "Launch with java debug enabled.") boolean debug; @Option(names = {"-h", "--help"}, usageHelp = true, description = "Display help/info") @@ -28,20 +28,23 @@ public class Main implements Callable { @Option(names = {"--version"}, versionHelp = true, arity = "0", description = "Display version info") boolean versionRequested; - @Parameters - String script; - - @Parameters(arity = "1..*") - List params; + @Parameters(index = "0", description = "A file with java code or if named .jsh will be run with jshell") + String scriptOrFile; + @Parameters(index = "1..*", arity = "0..*", description = "Parameters to pass on to the script") + List params = new ArrayList(); void info(String msg) { spec.commandLine().getErr().println(msg); } + void warn(String msg) { + info("[jbang] [WARNING] " + msg); + } + void quit(int status) { out.println(status == 0 ? "true" : "false"); - exit(status); + throw new ExitException(status); } static String USAGE = "j'bang - Enhanced scripting support for Java on *nix-based systems.\n" + "\n" @@ -52,12 +55,15 @@ void quit(int status) { private static File prepareScript; public static void main(String... args) throws FileNotFoundException { - var main = new Main(); - - int exitcode = new CommandLine(main).setStopAtPositional(true).setOut(new PrintWriter(err, true)).setErr(new PrintWriter(err, true)).execute(args); + int exitcode = getCommandLine().execute(args); System.exit(exitcode); } + static CommandLine getCommandLine() { + return new CommandLine(new Main()).setExitCodeExceptionMapper(new VersionProvider()).setStopAtPositional(true) + .setOut(new PrintWriter(err, true)).setErr(new PrintWriter(err, true)); + } + @Override public Integer call() throws FileNotFoundException { @@ -69,7 +75,15 @@ public Integer call() throws FileNotFoundException { quit(0); } - prepareScript = prepareScript(script); + String cmdline = generateCommandLine(); + + out.println(cmdline); + + return 0; + } + + String generateCommandLine() throws FileNotFoundException { + prepareScript = prepareScript(scriptOrFile); Scanner sc = new Scanner(prepareScript); sc.useDelimiter("\\Z"); @@ -79,32 +93,35 @@ public Integer call() throws FileNotFoundException { List dependencies = script.collectDependencies(); var classpath = new DependencyUtil().resolveDependencies(dependencies, Collections.emptyList(), true); - StringBuffer optionalArgs = new StringBuffer(" "); + List optionalArgs = new ArrayList(); var javacmd = "java"; - var sourceargs = " --source 11"; if (prepareScript.getName().endsWith(".jsh")) { javacmd = "jshell"; - sourceargs = ""; if (!classpath.isBlank()) { - optionalArgs.append("--class-path " + classpath); + optionalArgs.add("--class-path " + classpath); + } + if (debug) { + info("debug not possible when running via jshell."); } } else { - // optionalArgs.append(" - // -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"); + optionalArgs.add("--source 11"); + if (debug) { + optionalArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005"); + } if (!classpath.isBlank()) { - optionalArgs.append("-classpath " + classpath); + optionalArgs.add("-classpath " + classpath); } } - var cmdline = new StringBuffer(javacmd).append(optionalArgs).append(sourceargs) - .append(" " + getenv("JBANG_FILE")) - .append(" " + params.stream().skip(1).collect(Collectors.joining(" "))); + List fullArgs = new ArrayList<>(); + fullArgs.add(javacmd); + fullArgs.addAll(optionalArgs); + fullArgs.add(scriptOrFile); + fullArgs.addAll(params); - out.println(cmdline); - - return 0; + return fullArgs.stream().collect(Collectors.joining(" ")); } /** @@ -201,5 +218,4 @@ private static File prepareScript(String scriptresouce) { return scriptFile; } - } diff --git a/src/main/java/dk/xam/jbang/Util.java b/src/main/java/dk/xam/jbang/Util.java index 43c6626dd..e034cac45 100644 --- a/src/main/java/dk/xam/jbang/Util.java +++ b/src/main/java/dk/xam/jbang/Util.java @@ -20,7 +20,7 @@ static public void errorMsg(String msg) { static public void quit(int status) { System.out.print(status > 0 ? "true" : "false"); - System.exit(status); + throw new ExitException(status); } } diff --git a/src/main/java/dk/xam/jbang/VersionProvider.java b/src/main/java/dk/xam/jbang/VersionProvider.java index fe8e4105d..a3a4a60b1 100644 --- a/src/main/java/dk/xam/jbang/VersionProvider.java +++ b/src/main/java/dk/xam/jbang/VersionProvider.java @@ -2,9 +2,17 @@ import picocli.CommandLine; -public class VersionProvider implements CommandLine.IVersionProvider { +public class VersionProvider implements CommandLine.IVersionProvider, CommandLine.IExitCodeExceptionMapper { @Override public String[] getVersion() throws Exception { return new String[]{dk.xam.jbang.BuildConfig.VERSION}; } + + @Override + public int getExitCode(Throwable t) { + if (t instanceof ExitException) { + return ((ExitException) t).getStatus(); + } + return CommandLine.ExitCode.SOFTWARE; + } } diff --git a/src/test/java/dk/xam/jbang/TestArguments.java b/src/test/java/dk/xam/jbang/TestArguments.java index 377c65088..d635e63a7 100644 --- a/src/test/java/dk/xam/jbang/TestArguments.java +++ b/src/test/java/dk/xam/jbang/TestArguments.java @@ -1,47 +1,78 @@ package dk.xam.jbang; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import picocli.CommandLine; -import static org.junit.jupiter.api.Assertions.*; -import static org.hamcrest.Matchers.*; -import static org.hamcrest.MatcherAssert.assertThat; - class TestArguments { + private CommandLine cli; + private Main main; + + @BeforeEach + void setup() { + cli = Main.getCommandLine(); + main = cli.getCommand(); + } + @Test public void testBasicArguments() { - var arg = new Main(); - new CommandLine(arg).parseArgs("-h", "--debug", "myfile.java"); + cli.parseArgs("-h", "--debug", "myfile.java"); - assert arg.helpRequested; - assert arg.debug; - assertEquals(1, arg.params.size()); + assert main.helpRequested; + assertThat(main.debug, is(true)); + assertThat(main.scriptOrFile, is("myfile.java")); + assertThat(main.params.size(), is(0)); } @Test - public void testArgumentsForJbang() { - - var a = Main.argsForJbang("test.java"); - assertThat(a, is(new String[] {"test.java"})); - - a = Main.argsForJbang("--debug", "test.java"); - assertThat(a, is(new String[]{"--debug", "--", "test.java"})); + public void testDoubleDebug() { + cli.parseArgs("--debug", "test.java", "--debug", "wonka"); + assertThat(main.debug, is(true)); + assertThat(main.scriptOrFile, is("test.java")); + assertThat(main.params, is(Arrays.asList("--debug", "wonka"))); + } - a = Main.argsForJbang("test.java", "-h"); - assertThat(a, is(new String[]{"test.java", "-h"})); + /** + * @Test public void testInit() { cli.parseArgs("--init", "x.java", "y.java"); + * assertThat(main.script, is("x.java")); assertThat(main.params, + * is(Arrays.asList("x.java", "y.java"))); } + **/ - a = Main.argsForJbang("-", "--help"); - assertThat(a, is(new String[]{"-", "--help"})); + @Test + public void testStdInWithHelpParam() { + cli.parseArgs("-", "--help"); + assertThat(main.scriptOrFile, is("-")); + assertThat(main.helpRequested, is(false)); + assertThat(main.params, is(Arrays.asList("--help"))); + } - a = Main.argsForJbang("--init", "x.java", "y.java"); - assertThat(a, is(new String[]{"--init", "--", "x.java", "y.java"})); + @Test + public void testScriptWithHelpParam() { + cli.parseArgs("test.java", "-h"); + assertThat(main.scriptOrFile, is("test.java")); + assertThat(main.helpRequested, is(false)); + assertThat(main.params, is(Arrays.asList("-h"))); + } - a = Main.argsForJbang("--debug", "test.java", "--debug", "wonka"); - assertThat(a, is(new String[]{"--debug", "--", "test.java", "--debug", "wonka"})); + @Test + public void testDebugWithScript() { + cli.parseArgs("--debug", "test.java"); + assertThat(main.scriptOrFile, is("test.java")); + assertThat(main.debug, is(true)); + } + @Test + public void testSimpleScript() { + cli.parseArgs("test.java"); + assertThat(main.scriptOrFile, is("test.java")); } } diff --git a/src/test/java/dk/xam/jbang/TestMain.java b/src/test/java/dk/xam/jbang/TestMain.java index 10709070c..70d2aaeeb 100644 --- a/src/test/java/dk/xam/jbang/TestMain.java +++ b/src/test/java/dk/xam/jbang/TestMain.java @@ -1,4 +1,92 @@ package dk.xam.jbang; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.URISyntaxException; +import java.net.URL; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import picocli.CommandLine; + public class TestMain { + + public static final String EXAMPLES_FOLDER = "examples"; + static File examplesTestFolder; + + @BeforeAll + static void init() throws URISyntaxException { + URL examplesUrl = TestMain.class.getClassLoader().getResource(EXAMPLES_FOLDER); + examplesTestFolder = new File(new File(examplesUrl.toURI()).getAbsolutePath()); + } + + @Test + void testHelloWorld() throws FileNotFoundException { + + Main main = new Main(); + var arg = new File(examplesTestFolder, "helloworld.java").getAbsolutePath(); + new CommandLine(main).parseArgs(arg); + + String result = main.generateCommandLine().toString(); + + assertThat(result, startsWith("java")); + assertThat(result, containsString("helloworld.java")); + assertThat(result, containsString("--source 11")); + } + + @Test + void testHelloWorldShell() throws FileNotFoundException { + + Main main = new Main(); + var arg = new File(examplesTestFolder, "helloworld.jsh").getAbsolutePath(); + new CommandLine(main).parseArgs(arg); + + String result = main.generateCommandLine().toString(); + + assertThat(result, startsWith("jshell")); + assertThat(result, not(containsString(" "))); + assertThat(result, containsString("helloworld.jsh")); + assertThat(result, not(containsString("--source 11"))); + } + + @Test + void testDebug() throws FileNotFoundException { + + Main main = new Main(); + var arg = new File(examplesTestFolder, "helloworld.java").getAbsolutePath(); + new CommandLine(main).parseArgs("--debug", arg); + + String result = main.generateCommandLine().toString(); + + assertThat(result, startsWith("java ")); + assertThat(result, containsString("helloworld.java")); + assertThat(result, containsString(" --source 11 ")); + assertThat(result, containsString("jdwp")); + assertThat(result, not(containsString(" "))); + assertThat(result, not(containsString("classpath"))); + } + + @Test + void testDependencies() throws FileNotFoundException { + + Main main = new Main(); + var arg = new File(examplesTestFolder, "classpath_example.java").getAbsolutePath(); + new CommandLine(main).parseArgs(arg); + + String result = main.generateCommandLine().toString(); + + assertThat(result, startsWith("java ")); + assertThat(result, containsString("classpath_example.java")); + assertThat(result, containsString(" --source 11 ")); + assertThat(result, not(containsString(" "))); + assertThat(result, containsString("classpath")); + assertThat(result, containsString("log4j")); + } + + }