Jupyter bindings (#335)

This commit is contained in:
Marcin Kostrzewa 2019-11-18 14:36:03 +01:00 committed by GitHub
parent 6078b54f50
commit 51d66cdef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 312 additions and 181 deletions

View File

@ -148,6 +148,20 @@ JAVA_HOME=<PATH_TO_GRAAL_HOME> ./enso.jar <CLI_ARGS>
If you decide not to use the default launcher script, make sure to pass
the `-XX:-UseJVMCIClassLoader` option to the `java` command.
#### Installing the Jupyter kernel
Enso has a higly experimental and not-actively-maintained Jupyer Kernel.
To run it:
1. Build (or download from the CI server) the CLI Fat Jar.
2. Fill in the `engine/language-server/jupyter-kernel/enso/kernel.json`
file, providing correct paths to the `enso.jar` distribution
and GraalVM JAVA_HOME.
3. Run:
```
jupyter kernelspec install <ROOT_OF_THIS_REPO>/engine/language-server/jupyter-kernel/enso
```
Congratulations, your Jupyter Kernel should now be installed and ready to use.
#### Passing Debug Options
GraalVM provides some useful debugging options, including the ability to output
the compilation graph during JIT optimisation, and the ASM generated by the JIT.

View File

@ -53,7 +53,7 @@ jobs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/TEST-*.xml'
- script: |
sbt runtime/assembly
sbt language_server/assembly
displayName: Build the Uberjar
continueOnError: true
- task: CopyFiles@2

View File

@ -55,7 +55,6 @@ scalacOptions in ThisBuild ++= Seq(
"-Xlint:unsound-match", // Pattern match may not be typesafe.
"-Xmacro-settings:-logging@org.enso", // Disable the debug logging globally.
"-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
"-Ypartial-unification", // Enable partial unification (which is enabled by default in Scala 2.13).
"-Ypartial-unification", // Enable partial unification in type constructor inference
"-Ywarn-dead-code", // Warn when dead code is identified.
"-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
@ -311,19 +310,6 @@ val truffleRunOptionsSettings = Seq(
lazy val runtime = (project in file("engine/runtime"))
.settings(
mainClass in (Compile, run) := Some("org.enso.interpreter.Main"),
mainClass in assembly := (Compile / run / mainClass).value,
assemblyJarName in assembly := "enso.jar",
test in assembly := {},
assemblyOutputPath in assembly := file("enso.jar"),
assemblyOption in assembly := (assemblyOption in assembly).value.copy(
prependShellScript = Some(
defaultUniversalScript(
shebang = false,
javaOpts = truffleRunOptions
)
)
),
version := "0.1",
commands += WithDebugCommand.withDebug,
inConfig(Compile)(truffleRunOptionsSettings),
@ -344,8 +330,7 @@ lazy val runtime = (project in file("engine/runtime"))
"org.scalacheck" %% "scalacheck" % "1.14.0" % Test,
"org.scalactic" %% "scalactic" % "3.0.8" % Test,
"org.scalatest" %% "scalatest" % "3.2.0-SNAP10" % Test,
"org.typelevel" %% "cats-core" % "2.0.0-M4",
"commons-cli" % "commons-cli" % "1.4"
"org.typelevel" %% "cats-core" % "2.0.0-M4"
),
libraryDependencies ++= jmh
)
@ -360,20 +345,6 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(Def.task { (Compile / sourceManaged).value.mkdirs })
.value
)
.settings(
buildNativeImage := Def
.task {
val javaHome = System.getProperty("java.home")
val nativeImagePath = s"$javaHome/bin/native-image"
val classPath = (Runtime / fullClasspath).value.files.mkString(":")
val resourcesGlobOpt = "-H:IncludeResources=.*Main.enso$"
val cmd =
s"$nativeImagePath $resourcesGlobOpt --macro:truffle --no-fallback --initialize-at-build-time -cp $classPath ${(Compile / mainClass).value.get} enso"
cmd !
}
.dependsOn(Compile / compile)
.value
)
.configs(Benchmark)
.settings(
logBuffered := false,
@ -394,3 +365,43 @@ lazy val runtime = (project in file("engine/runtime"))
)
.dependsOn(pkg)
.dependsOn(syntax)
lazy val language_server = project
.in(file("engine/language-server"))
.settings(
mainClass in (Compile, run) := Some("org.enso.languageserver.Main"),
mainClass in assembly := (Compile / run / mainClass).value,
assemblyJarName in assembly := "enso.jar",
test in assembly := {},
assemblyOutputPath in assembly := file("enso.jar"),
assemblyOption in assembly := (assemblyOption in assembly).value.copy(
prependShellScript = Some(
defaultUniversalScript(
shebang = false,
javaOpts = truffleRunOptions
)
)
),
inConfig(Compile)(truffleRunOptionsSettings),
libraryDependencies ++= Seq(
"org.graalvm.sdk" % "polyglot-tck" % graalVersion % "provided",
"commons-cli" % "commons-cli" % "1.4",
"io.github.spencerpark" % "jupyter-jvm-basekernel" % "2.3.0"
)
)
.settings(
buildNativeImage := Def
.task {
val javaHome = System.getProperty("java.home")
val nativeImagePath = s"$javaHome/bin/native-image"
val classPath = (Runtime / fullClasspath).value.files.mkString(":")
val resourcesGlobOpt = "-H:IncludeResources=.*Main.enso$"
val cmd =
s"$nativeImagePath $resourcesGlobOpt --macro:truffle --no-fallback --initialize-at-build-time -cp $classPath ${(Compile / mainClass).value.get} enso"
cmd !
}
.dependsOn(Compile / compile)
.value
)
.dependsOn(runtime)
.dependsOn(pkg)

View File

@ -0,0 +1,15 @@
{
"argv": [
"java",
"-XX:-UseJVMCIClassLoader",
"-jar",
"<ABSOLUTE_PATH_TO_ENSO_JAR>",
"--jupyter-kernel",
"{connection_file}"
],
"display_name": "Enso",
"language": "Enso",
"env": {
"JAVA_HOME": "<GRAAL_VM_JAVA_HOME>"
}
}

View File

@ -0,0 +1,36 @@
package org.enso.languageserver
import java.io.InputStream
import java.io.OutputStream
import org.enso.interpreter.Constants
import org.enso.interpreter.runtime.RuntimeOptions
import org.graalvm.polyglot.Context
/**
* Utility class for creating Graal polyglot contexts.
*/
class ContextFactory {
/**
* Creates a new Graal polyglot context.
*
* @param packagesPath Enso packages path
* @param in the input stream for standard in
* @param out the output stream for standard out
* @return configured Context instance
*/
def create(
packagesPath: String = "",
in: InputStream = System.in,
out: OutputStream = System.out
): Context =
Context
.newBuilder(Constants.LANGUAGE_ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.getPackagesPathOption, packagesPath)
.out(out)
.in(in)
.build
}

View File

@ -0,0 +1,62 @@
package org.enso.languageserver
import java.nio.file.Files
import java.nio.file.Paths
import java.util.logging.Level
import io.github.spencerpark.jupyter.channels.JupyterConnection
import io.github.spencerpark.jupyter.channels.JupyterSocket
import io.github.spencerpark.jupyter.kernel.BaseKernel
import io.github.spencerpark.jupyter.kernel.KernelConnectionProperties
import io.github.spencerpark.jupyter.kernel.LanguageInfo
import io.github.spencerpark.jupyter.kernel.display.DisplayData
import org.enso.interpreter.Constants
import org.graalvm.polyglot.Context
/**
* A wrapper for Enso interpreter for use by Jupyter
*/
class JupyterKernel extends BaseKernel {
private val context: Context =
new ContextFactory().create("", getIO.in, getIO.out)
/**
* Evaluates Enso code in the context of Jupyter request
*
* @param expr the expression to execute
* @return the Jupyter-friendly representation of the result of executing `expr`
*/
override def eval(expr: String) =
new DisplayData(context.eval(Constants.LANGUAGE_ID, expr).toString)
/**
* Basic language information to display in Jupyter
* @return the basic language information object
*/
override def getLanguageInfo: LanguageInfo =
new LanguageInfo.Builder(Constants.LANGUAGE_ID)
.version(Constants.LANGUAGE_VERSION)
.build
/**
* Starts the Jupyter kernel server
* @param connectionFileStr filepath of the Jupyter connection file
*/
def run(connectionFileStr: String): Unit = {
val connectionFile = Paths.get(connectionFileStr)
if (!Files.isRegularFile(connectionFile))
throw new IllegalArgumentException(
"Connection file '" + connectionFile + "' isn't a file."
)
val contents = new String(Files.readAllBytes(connectionFile))
JupyterSocket.JUPYTER_LOGGER.setLevel(Level.WARNING)
val connProps = KernelConnectionProperties.parse(contents)
val connection = new JupyterConnection(connProps)
becomeHandlerForConnection(connection)
connection.connect()
connection.waitUntilClose()
}
}

View File

@ -0,0 +1,143 @@
package org.enso.languageserver
import org.apache.commons.cli._
import org.enso.interpreter.Constants
import org.enso.pkg.Package
import org.graalvm.polyglot.Source
import java.io.File
import scala.util.Try
/** The main CLI entry point class. */
object Main {
private val RUN_OPTION = "run"
private val HELP_OPTION = "help"
private val NEW_OPTION = "new"
private val JUPYTER_OPTION = "jupyter-kernel"
/**
* Builds the [[Options]] object representing the CLI syntax.
*
* @return an [[Options]] object representing the CLI syntax
*/
private def buildOptions = {
val help = Option
.builder("h")
.longOpt(HELP_OPTION)
.desc("Displays this message.")
.build
val run = Option.builder
.hasArg(true)
.numberOfArgs(1)
.argName("file")
.longOpt(RUN_OPTION)
.desc("Runs a specified Enso file.")
.build
val newOpt = Option.builder
.hasArg(true)
.numberOfArgs(1)
.argName("path")
.longOpt(NEW_OPTION)
.desc("Creates a new Enso project.")
.build
val jupyterOption = Option.builder
.hasArg(true)
.numberOfArgs(1)
.argName("connection file")
.longOpt(JUPYTER_OPTION)
.desc("Runs Enso Jupyter Kernel.")
.build
val options = new Options
options
.addOption(help)
.addOption(run)
.addOption(newOpt)
.addOption(jupyterOption)
options
}
/**
* Prints the help message to the standard output.
*
* @param options object representing the CLI syntax
*/
private def printHelp(options: Options): Unit =
new HelpFormatter().printHelp(Constants.LANGUAGE_ID, options)
/** Terminates the process with a failure exit code. */
private def exitFail(): Unit = System.exit(1)
/** Terminates the process with a success exit code. */
private def exitSuccess(): Unit = System.exit(0)
/**
* Handles the `--new` CLI option.
*
* @param path root path of the newly created project
*/
private def createNew(path: String) {
Package.getOrCreate(new File(path))
exitSuccess()
}
/**
* Handles the `--run` CLI option.
*
* @param path path of the project or file to execute
*/
private def run(path: String): Unit = {
val file = new File(path)
if (!file.exists) {
System.out.println("File " + file + " does not exist.")
exitFail()
}
val projectMode = file.isDirectory
val packagePath =
if (projectMode) file.getAbsolutePath
else ""
var mainLocation = file
if (projectMode) {
val pkg = Package.fromDirectory(file)
val main = pkg.map(_.mainFile)
if (!main.exists(_.exists)) {
println("Main file does not exist.")
exitFail()
}
mainLocation = main.get
}
val context = new ContextFactory().create(packagePath)
val source = Source.newBuilder(Constants.LANGUAGE_ID, mainLocation).build
context.eval(source)
exitSuccess()
}
/**
* Main entry point for the CLI program.
*
* @param args the command line arguments
*/
def main(args: Array[String]): Unit = {
val options = buildOptions
val parser = new DefaultParser
val line: CommandLine = Try(parser.parse(options, args)).getOrElse {
printHelp(options)
exitFail()
return
}
if (line.hasOption(HELP_OPTION)) {
printHelp(options)
exitSuccess()
}
if (line.hasOption(NEW_OPTION)) {
createNew(line.getOptionValue(NEW_OPTION))
}
if (line.hasOption(RUN_OPTION)) {
run(line.getOptionValue(RUN_OPTION))
}
if (line.hasOption(JUPYTER_OPTION)) {
new JupyterKernel().run(line.getOptionValue(JUPYTER_OPTION))
}
printHelp(options)
exitFail()
}
}

View File

@ -1,150 +0,0 @@
package org.enso.interpreter;
import org.apache.commons.cli.*;
import org.enso.interpreter.runtime.RuntimeOptions;
import org.enso.interpreter.util.ScalaConversions;
import org.enso.pkg.Package;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
/** The main CLI entry point class. */
public class Main {
private static final String RUN_OPTION = "run";
private static final String HELP_OPTION = "help";
private static final String NEW_OPTION = "new";
/**
* Builds the {@link Options} object representing the CLI syntax.
*
* @return an {@link Options} object representing the CLI syntax
*/
private static Options buildOptions() {
Option help = Option.builder("h").longOpt(HELP_OPTION).desc("Displays this message.").build();
Option run =
Option.builder()
.hasArg(true)
.numberOfArgs(1)
.argName("file")
.longOpt(RUN_OPTION)
.desc("Runs a specified Enso file.")
.build();
Option newOpt =
Option.builder()
.hasArg(true)
.numberOfArgs(1)
.argName("path")
.longOpt(NEW_OPTION)
.desc("Creates a new Enso project.")
.build();
Options options = new Options();
options.addOption(help).addOption(run).addOption(newOpt);
return options;
}
/**
* Prints the help message to the standard output.
*
* @param options object representing the CLI syntax
*/
private static void printHelp(Options options) {
new HelpFormatter().printHelp(Constants.LANGUAGE_ID, options);
}
/** Terminates the process with a failure exit code. */
private static void exitFail() {
System.exit(1);
}
/** Terminates the process with a success exit code. */
private static void exitSuccess() {
System.exit(0);
}
/**
* Handles the {@code --new} CLI option.
*
* @param path root path of the newly created project
*/
private static void createNew(String path) {
Package.getOrCreate(new File(path));
exitSuccess();
}
/**
* Handles the {@code --run} CLI option.
*
* @param path path of the project or file to execute
* @throws IOException when source code cannot be parsed
*/
private static void run(String path) throws IOException {
File file = new File(path);
if (!file.exists()) {
System.out.println("File " + file + " does not exist.");
exitFail();
}
boolean projectMode = file.isDirectory();
String packagePath = projectMode ? file.getAbsolutePath() : "";
File mainLocation = file;
if (projectMode) {
Optional<Package> pkg = ScalaConversions.asJava(Package.fromDirectory(file));
Optional<File> main = pkg.map(Package::mainFile);
if (!main.isPresent() || !main.get().exists()) {
System.out.println("Main file does not exist.");
exitFail();
}
mainLocation = main.get();
}
Context context =
Context.newBuilder(Constants.LANGUAGE_ID)
.allowExperimentalOptions(true)
.allowAllAccess(true)
.option(RuntimeOptions.getPackagesPathOption(), packagePath)
.out(System.out)
.in(System.in)
.build();
Source source = Source.newBuilder(Constants.LANGUAGE_ID, mainLocation).build();
context.eval(source);
exitSuccess();
}
/**
* Main entry point for the CLI program.
*
* @param args the command line arguments
*/
public static void main(String[] args) throws IOException {
Options options = buildOptions();
CommandLineParser parser = new DefaultParser();
CommandLine line;
try {
line = parser.parse(options, args);
} catch (ParseException e) {
printHelp(options);
exitFail();
return;
}
if (line.hasOption(HELP_OPTION)) {
printHelp(options);
exitSuccess();
return;
}
if (line.hasOption(NEW_OPTION)) {
createNew(line.getOptionValue(NEW_OPTION));
}
if (line.hasOption(RUN_OPTION)) {
run(line.getOptionValue(RUN_OPTION));
}
printHelp(options);
exitFail();
}
}