Skip to content
Snippets Groups Projects
Commit 9bfe9940 authored by dgelessus's avatar dgelessus
Browse files

Make the kernel jar self-installable

The jar can now be run with the "install" option to automatically copy
itself into the ProB home directory, and generate and install a Jupyter
kernel spec.

The build.gradle no longer generates the kernel spec itself, it now
runs the kernel jar to do that.
parent cd2f78b7
No related branches found
No related tags found
No related merge requests found
...@@ -2,9 +2,6 @@ ...@@ -2,9 +2,6 @@
.gradle/ .gradle/
/build/ /build/
# Generated
/kernelspec/prob2/kernel.json
# Python virtual environment # Python virtual environment
/env/ /env/
......
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
...@@ -53,37 +51,31 @@ tasks.withType(JavaCompile) { ...@@ -53,37 +51,31 @@ tasks.withType(JavaCompile) {
mainClassName = "de.prob2.jupyter.Main" mainClassName = "de.prob2.jupyter.Main"
final KERNEL_SPEC_PATH = project.projectDir.toPath().resolve(Paths.get("kernelspec", "prob2")) final KERNEL_SPEC_FILES_PATH = sourceSets.main.resources.sourceDirectories.singleFile.toPath().resolve(Paths.get("de", "prob2", "jupyter", "kernelspecfiles"))
final KERNEL_JSON_PATH = KERNEL_SPEC_PATH.resolve("kernel.json") final KERNEL_SPEC_OUTPUT_PATH = project.buildDir.toPath().resolve(Paths.get("kernelspec"))
task createKernelSpec {
dependsOn = [shadowJar]
ext.probHome = project.hasProperty("probHome") ? project.probHome : null
inputs.property("probHome", probHome).optional(true)
outputs.files(KERNEL_JSON_PATH.toFile())
doFirst { task cleanKernelSpec(type: Delete) {
final jarPath = shadowJar.archivePath delete = [KERNEL_SPEC_OUTPUT_PATH.toFile()]
final probHomeDef = probHome != null ? "\n\t\t\"-Dprob.home=$probHome\"," : ""
final jsonText = """{
\t"argv": [
\t\t"java",$probHomeDef
\t\t"-jar",
\t\t"$jarPath",
\t\t"{connection_file}"
\t],
\t"display_name": "ProB 2",
\t"language": "prob"
} }
""" clean.dependsOn << cleanKernelSpec
Files.write(KERNEL_JSON_PATH, jsonText.getBytes(StandardCharsets.UTF_8))
task createKernelSpec(type: JavaExec) {
dependsOn = [cleanKernelSpec, shadowJar]
main = project.mainClassName
args = ["createKernelSpec", KERNEL_SPEC_OUTPUT_PATH.toString()]
classpath(shadowJar.archivePath)
if (project.hasProperty("probHome")) {
systemProperty("prob.home", project.probHome)
}
inputs.dir(KERNEL_SPEC_FILES_PATH.toFile())
outputs.dir(KERNEL_SPEC_OUTPUT_PATH.toFile())
doFirst {
mkdir(KERNEL_SPEC_OUTPUT_PATH)
} }
} }
task installKernelSpec(type: Exec) { task installKernelSpec(type: Exec) {
dependsOn = [createKernelSpec] dependsOn = [createKernelSpec]
executable = "python3" executable = project.hasProperty("pythonInterpreter") ? project.pythonInterpreter : "python3"
args = ["-m", "jupyter", "kernelspec", "install", "--sys-prefix", KERNEL_SPEC_PATH.toString()] args = ["-m", "jupyter", "kernelspec", "install", "--sys-prefix", "--name=prob2", KERNEL_SPEC_OUTPUT_PATH.toString()]
}
if (hasProperty("pythonInterpreter")) {
installKernelSpec.executable = pythonInterpreter
} }
package de.prob2.jupyter; package de.prob2.jupyter;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.CodeSource;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
...@@ -15,6 +25,8 @@ import com.google.inject.Stage; ...@@ -15,6 +25,8 @@ import com.google.inject.Stage;
import io.github.spencerpark.jupyter.channels.JupyterConnection; import io.github.spencerpark.jupyter.channels.JupyterConnection;
import io.github.spencerpark.jupyter.kernel.KernelConnectionProperties; import io.github.spencerpark.jupyter.kernel.KernelConnectionProperties;
import org.jetbrains.annotations.Nullable;
public final class Main { public final class Main {
private Main() { private Main() {
super(); super();
...@@ -22,18 +34,161 @@ public final class Main { ...@@ -22,18 +34,161 @@ public final class Main {
throw new AssertionError(); throw new AssertionError();
} }
public static void main(final String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException { private static AssertionError die(final int status, final @Nullable Throwable cause) {
if (args.length != 1) { System.exit(status);
System.err.printf("Expected exactly one argument, not %d%n", args.length); return new AssertionError("Unreachable", cause);
System.exit(2);
} }
System.setProperty("prob.stdlib", Paths.get(de.prob.Main.getProBDirectory(), "stdlib").toString()); private static AssertionError die(final int status) {
return die(status, null);
}
private static Path getJarPath() {
try {
final CodeSource cs = Main.class.getProtectionDomain().getCodeSource();
if (cs == null) {
System.err.println("Unable to determine location of kernel jar file (CodeSource is null)");
throw die(1);
}
return Paths.get(cs.getLocation().toURI());
} catch (final RuntimeException | URISyntaxException e) {
System.err.println("Unable to determine location of kernel jar file");
e.printStackTrace();
throw die(1, e);
}
}
private static Path getDestPath(final Path jarPath) {
return Paths.get(de.prob.Main.getProBDirectory(), "jupyter", jarPath.getFileName().toString());
}
private static void copyJar(final Path jarPath, final Path destPath) {
System.out.println("Installing kernel jar file...");
System.out.println("Path to kernel jar file: " + jarPath);
System.out.println("Kernel jar will be copied to: " + destPath);
try {
Files.createDirectories(destPath.getParent());
} catch (final IOException e) {
System.err.println("Failed to create destination directory");
e.printStackTrace();
throw die(1, e);
}
try {
Files.copy(jarPath, destPath, StandardCopyOption.REPLACE_EXISTING);
} catch (final IOException e) {
System.err.println("Failed to copy kernel jar file");
e.printStackTrace();
throw die(1, e);
}
System.out.println("Kernel jar file installed");
}
final Path connectionFile = Paths.get(args[0]); private static void createKernelSpec(final Path jarPath, final Path kernelSpecDir) {
System.out.println("Creating kernel spec...");
System.out.println("Path to kernel jar file: " + jarPath);
System.out.println("Kernel spec directory: " + kernelSpecDir);
Stream.of("kernel.js", "logo-32x32.png", "logo-64x64.png").forEach(name -> {
System.out.println("Extracting: " + name);
try (final InputStream is = Main.class.getResourceAsStream("kernelspecfiles/" + name)) {
Files.copy(is, kernelSpecDir.resolve(name));
} catch (final IOException e) {
System.err.println("Failed to extract kernel spec file: " + name);
e.printStackTrace();
throw die(1, e);
}
});
final String probHome = System.getProperty("prob.home");
final String probHomeDef;
if (probHome != null) {
System.out.println("prob.home is set, adding a corresponding prob.home defintion to kernel.json: " + probHome);
probHomeDef = String.format("\n\t\t\"-Dprob.home=%s\",", probHome);
} else {
System.out.println("prob.home is not set, not adding a prob.home definition to kernel.json");
probHomeDef = "";
}
System.out.println("Creating kernel.json");
try (
final InputStream is = Main.class.getResourceAsStream("kernelspecfiles/kernel.json.template");
final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
final BufferedReader br = new BufferedReader(isr);
) {
final String kernelJsonText = String.format(br.lines().collect(Collectors.joining("\n")), probHomeDef, jarPath);
Files.write(kernelSpecDir.resolve("kernel.json"), Arrays.asList(kernelJsonText.split("\n")));
} catch (final IOException e) {
System.err.println("Failed to create kernel.json");
e.printStackTrace();
throw die(1, e);
}
System.out.println("Kernel spec created");
}
private static void installKernelSpec(final Path pythonInterpreter, final Path kernelSpecDir) {
System.out.println("Installing kernel spec...");
System.out.println("Python interpreter: " + pythonInterpreter);
System.out.println("Kernel spec directory: " + kernelSpecDir);
final ProcessBuilder pb = new ProcessBuilder(pythonInterpreter.toString(), "-m", "jupyter", "kernelspec", "install", "--sys-prefix", "--name=prob2", kernelSpecDir.toString());
pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
final Process pythonProcess;
try {
pythonProcess = pb.start();
} catch (final IOException e) {
System.err.println("Failed to install kernel spec");
e.printStackTrace();
throw die(1, e);
}
final int statusCode;
try {
statusCode = pythonProcess.waitFor();
} catch (final InterruptedException e) {
System.err.println("Interrupted");
e.printStackTrace();
Thread.currentThread().interrupt();
throw die(1, e);
}
if (statusCode != 0) {
System.err.println("Python exited with status " + statusCode);
throw die(1);
}
System.out.println("Kernel spec installed");
}
private static void install(final Path pythonInterpreter) {
final Path jarPath = getJarPath();
final Path destPath = getDestPath(jarPath);
copyJar(jarPath, destPath);
try {
final Path kernelSpecDir = Files.createTempDirectory("prob2kernelspec");
createKernelSpec(destPath, kernelSpecDir);
installKernelSpec(pythonInterpreter, kernelSpecDir);
try (final Stream<Path> contents = Files.list(kernelSpecDir)) {
contents.forEach(path -> {
try {
Files.delete(path);
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
});
}
Files.delete(kernelSpecDir);
} catch (final IOException | UncheckedIOException e) {
System.err.println("Failed to create kernel spec");
e.printStackTrace();
throw die(1, e);
}
System.out.println("The ProB 2 Jupyter kernel has been installed.");
System.out.println("To use it, start Jupyter Notebook and select \"ProB 2\" when creating a new notebook.");
System.out.println("This jar file can be safely deleted after installation.");
}
private static void startKernel(final Path connectionFile) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
final String contents = String.join("\n", Files.readAllLines(connectionFile, StandardCharsets.UTF_8)); final String contents = String.join("\n", Files.readAllLines(connectionFile, StandardCharsets.UTF_8));
final JupyterConnection conn = new JupyterConnection(KernelConnectionProperties.parse(contents)); final JupyterConnection conn = new JupyterConnection(KernelConnectionProperties.parse(contents));
System.setProperty("prob.stdlib", Paths.get(de.prob.Main.getProBDirectory(), "stdlib").toString());
final Injector injector = Guice.createInjector(Stage.PRODUCTION, new ProBKernelModule()); final Injector injector = Guice.createInjector(Stage.PRODUCTION, new ProBKernelModule());
final ProBKernel kernel = injector.getInstance(ProBKernel.class); final ProBKernel kernel = injector.getInstance(ProBKernel.class);
kernel.becomeHandlerForConnection(conn); kernel.becomeHandlerForConnection(conn);
...@@ -41,4 +196,51 @@ public final class Main { ...@@ -41,4 +196,51 @@ public final class Main {
conn.connect(); conn.connect();
conn.waitUntilClose(); conn.waitUntilClose();
} }
public static void main(final String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
if (args.length < 1 || args.length == 1 && "--help".equals(args[0])) {
System.err.println("Usage: java -jar prob2-jupyter-kernel-all.jar [--help | install PYTHONINTERPRETER | createKernelSpec KERNELSPECDIR | run CONNECTIONFILE]");
System.err.println("--help: Prints this information.");
System.err.println("install: Copies the kernel into the ProB home directory, and installs the kernel in the given Python interpreter.");
System.err.println("\tIf you're not sure which Python interpreter is used by your Jupyter installation, open a Python notebook and run \"import sys; print(sys.executable)\".");
System.err.println("createKernelSpec: Creates a Jupyter kernel spec for this jar file at the given location.");
System.err.println("\tThis option is for advanced users or developers, who don't want the jar file to be copied, or who want to install the kernel spec manually.");
System.err.println("run: Runs the kernel using the given connection file.");
System.err.println("\tThis option is not meant to be used manually, it is used internally when Jupyter starts the kernel.");
throw die(2);
}
switch (args[0]) {
case "install":
if (args.length != 2) {
System.err.println("install expects exactly one argument, not " + (args.length-1));
System.err.println("Use --help for more info.");
throw die(2);
}
install(Paths.get(args[1]));
break;
case "createKernelSpec":
if (args.length != 2) {
System.err.println("createKernelSpec expects exactly one argument, not " + (args.length-1));
System.err.println("Use --help for more info.");
throw die(2);
}
createKernelSpec(getJarPath(), Paths.get(args[1]));
break;
case "run":
if (args.length != 2) {
System.err.println("run expects exactly one argument, not " + (args.length-1));
System.err.println("Use --help for more info.");
throw die(2);
}
startKernel(Paths.get(args[1]));
break;
default:
System.err.println("Unknown subcommand: " + args[0]);
System.err.println("Use --help for more info.");
throw die(2);
}
}
} }
{
"argv": [
"java",%s
"-jar",
"%s",
"run",
"{connection_file}"
],
"display_name": "ProB 2",
"language": "prob"
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment