diff --git a/jitpack.yml b/jitpack.yml new file mode 100755 index 0000000..377a04b --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,13 @@ +before_install: +# Setup mise environment + - curl https://mise.run | sh +# the following installs the right tools and creates a mise.toml +# you can skip these if you commit a mise.toml to the project. + - ~/.local/bin/mise use java@17 maven@3.9 + + +install: +# you could technically just run mvn install directly +# put just showing mise x as an option where you can +# configure additional jdks. + - ~/.local/bin/mise x -- mvn install -B -ntp -DskipTests diff --git a/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java b/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java index 8f90e80..dc07700 100644 --- a/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java +++ b/jjava/src/main/java/org/dflib/jjava/magics/MavenResolver.java @@ -31,6 +31,8 @@ import org.apache.maven.model.building.DefaultModelBuilderFactory; import org.apache.maven.model.building.ModelBuilder; import org.apache.maven.model.building.ModelBuildingRequest; +import org.dflib.jjava.JJava; +import org.dflib.jjava.JavaKernel; import org.dflib.jjava.jupyter.Extension; import org.dflib.jjava.jupyter.ExtensionLoader; import org.dflib.jjava.jupyter.kernel.magic.registry.CellMagic; @@ -51,6 +53,9 @@ import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -409,4 +414,106 @@ public void loadFromPOM(List args) { throw new RuntimeException(message, e); } } + + /** + * Builds a JBang script and adds the resolved dependencies to the classpath. + * @param args + * @throws IOException + * @throws InterruptedException + */ + @LineMagic + public void jbang(List args) throws IOException, InterruptedException { + if (args.isEmpty()) { + throw new IllegalArgumentException("Loading from JBang requires at least a path to a JBang script reference."); + } + + MagicsArgs schema = MagicsArgs.builder() + .required("scriptRef") + .onlyKnownKeywords().onlyKnownFlags().build(); + + Map> vals = schema.parse(args); + String scriptRef = vals.get("scriptRef").get(0); + + ProcessBuilder pb = new ProcessBuilder("jbang", "build", scriptRef); + pb.redirectErrorStream(false); + Process process = pb.start(); + + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new RuntimeException("Building failed with exit code " + exitCode); + } + + try { + List resolvedDependencies = getJBangResolvedDependencies(scriptRef, null, true); + addJarsToClasspath(resolvedDependencies); + // let the kernel evaluate the body after the dependencies are resolved + // TODO: this is not right way as extensions can't give callback + //JavaKernel kernel = JJava.getKernproelInstance(); + //kernel.eval(body); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @CellMagic("jbang") + public void handleJBang(List args, String body) throws Exception { + try { + List resolvedDependencies = getJBangResolvedDependencies("-", body,false); + addJarsToClasspath(resolvedDependencies); + // let the kernel evaluate the body after the dependencies are resolved + // TODO: this is not right way as extensions can't give callback + //JavaKernel kernel = JJava.getKernelInstance(); + //kernel.eval(body); + } catch (Exception e) { + throw new RuntimeException(e); + } +} + + List getJBangResolvedDependencies(String scriptRef, String body, boolean inclAppJar) throws IOException { + try { + ProcessBuilder pb = new ProcessBuilder("jbang", "info", "tools", scriptRef); + pb.redirectErrorStream(false); + Process process = pb.start(); + + if(body != null) { + try (java.io.OutputStream os = process.getOutputStream()) { + os.write(body.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + os.flush(); + } + } + + StringBuilder output = new StringBuilder(); + try (java.io.InputStream is = process.getInputStream(); + java.util.Scanner scanner = new java.util.Scanner(is, java.nio.charset.StandardCharsets.UTF_8.name())) { + while (scanner.hasNextLine()) { + output.append(scanner.nextLine()).append(System.lineSeparator()); + } + } + + int exitCode = process.waitFor(); + if (exitCode != 0) { + throw new RuntimeException("jbang info tools failed with exit code " + exitCode + ":\n" + output); + } + + + JsonObject json = JsonParser.parseString(output.toString()).getAsJsonObject(); + + List resolvedDependencies = Collections.emptyList(); + if (json.has("resolvedDependencies") && json.get("resolvedDependencies").isJsonArray()) { + resolvedDependencies = StreamSupport.stream(json.getAsJsonArray("resolvedDependencies").spliterator(), false) + .map(e -> e.getAsString()) + .collect(Collectors.toList()); + } + + if(inclAppJar) { + resolvedDependencies.add(json.get("applicationJar").getAsString()); + } + + return resolvedDependencies; + + + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java b/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java index 666ae20..544aba4 100644 --- a/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java +++ b/jjava/src/test/java/org/dflib/jjava/jupyter/kernel/KernelMagicIT.java @@ -95,4 +95,18 @@ void loadFromPOM() throws Exception { assertThat(snippetResult.getStderr(), not(containsString("|"))); assertThat(snippetResult.getStdout(), containsString("jakarta.annotation.Nullable")); } + + @Test + void loadFromJBang() throws Exception { + String snippet = String.join("\n", + "%%jbang", + "//DEPS jakarta.annotation:jakarta.annotation-api:3.0.0", + "import jakarta.annotation.Nullable;", + "Nullable.class.getName()" + ); + Container.ExecResult snippetResult = executeInKernel(snippet); + + assertThat(snippetResult.getStderr(), not(containsString("|"))); + assertThat(snippetResult.getStdout(), containsString("jakarta.annotation.Nullable")); + } }