Skip to content

Commit

Permalink
Use enso.dev.insight property to turn Insight on (#11385)
Browse files Browse the repository at this point in the history
  • Loading branch information
JaroslavTulach authored Oct 24, 2024
1 parent 3515976 commit fe45da9
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 22 deletions.
24 changes: 15 additions & 9 deletions engine/common/src/main/java/org/enso/common/ContextFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public final class ContextFactory {
private OutputStream out = System.out;
private OutputStream err = System.err;
private MessageTransport messageTransport;
private Level logLevel;
private Level logLevel = Level.INFO;
private boolean logMasking;
private boolean enableIrCaches;
private boolean disablePrivateCheck;
Expand Down Expand Up @@ -166,7 +166,6 @@ public Context build() {
.allowExperimentalOptions(true)
.allowAllAccess(true)
.allowHostAccess(allWithTypeMapping())
.option(RuntimeOptions.PROJECT_ROOT, projectRoot)
.option(RuntimeOptions.STRICT_ERRORS, Boolean.toString(strictErrors))
.option(RuntimeOptions.DISABLE_LINTING, Boolean.toString(disableLinting))
.option(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS, "true")
Expand All @@ -183,6 +182,7 @@ public Context build() {
.out(out)
.err(err)
.in(in);

if (checkForWarnings != null) {
builder.option(DebugServerInfo.METHOD_BREAKPOINT_OPTION, checkForWarnings);
}
Expand All @@ -206,12 +206,15 @@ public Context build() {
}
builder.logHandler(logHandler);

var graalpy =
new File(
new File(new File(new File(new File(projectRoot), "polyglot"), "python"), "bin"),
"graalpy");
if (graalpy.exists()) {
builder.option("python.Executable", graalpy.getAbsolutePath());
if (projectRoot != null) {
builder.option(RuntimeOptions.PROJECT_ROOT, projectRoot);
var graalpy =
new File(
new File(new File(new File(new File(projectRoot), "polyglot"), "python"), "bin"),
"graalpy");
if (graalpy.exists()) {
builder.option("python.Executable", graalpy.getAbsolutePath());
}
}
if (ENGINE_HAS_JAVA) {
var javaHome = System.getProperty("java.home");
Expand All @@ -224,7 +227,10 @@ public Context build() {
.option("java.UseBindingsLoader", "true")
.allowCreateThread(true);
}
return builder.build();

var ctx = builder.build();
ContextInsightSetup.configureContext(ctx);
return ctx;
}

/**
Expand Down
102 changes: 102 additions & 0 deletions engine/common/src/main/java/org/enso/common/ContextInsightSetup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.enso.common;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Function;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;

/**
* Development support for running <a
* href="https://www.graalvm.org/latest/tools/graalvm-insight/">GraalVM Insight</a> scripts in the
* Enso execution enviroments. Works both - in the CLI as well as in IDE. To use specify JVM
* property {@link #INSIGHT_PROP} when executing the CLI:
*
* <pre>
* enso --vm.D=enso.dev.insight=insightScript.js
* </pre>
*
* or when launching the {@code project-manager}:
*
* <pre>
* ENSO_JVM_OPTS=-Denso.dev.insight=`pwd`/insightScript.js project-manager
* </pre>
*
* The sample {@code insightScript.js} can look for example like:
*
* <pre>
* print("Initializing Insight: " + insight);
* insight.on("enter", function(ctx) {
* print("Calling " + ctx.name);
* }, {
* roots: true
* });
* </pre>
*
* More information about Insight scripts can be found in the <a
* href="https://www.graalvm.org/latest/tools/graalvm-insight/manual/">Insight manual</a> and <a
* href="https://www.graalvm.org/tools/javadoc/org/graalvm/tools/insight/Insight.html">programatic
* documentation</a>.
*/
final class ContextInsightSetup {
private static final String INSIGHT_PROP = "enso.dev.insight";
private static ContextInsightSetup ACTIVE;

private final Context ctx;
private final Path insightFile;
private AutoCloseable insightHandle;

private ContextInsightSetup(Context ctx, Path file) {
this.ctx = ctx;
this.insightFile = file;
}

/**
* Configures the context if {@link #INSIGHT_PROP} property is specified. This support is
* <em>development only</em>. It can be (and will be) removed in the future.
*
* @param ctx context to configure
* @throws AssertionError throws assertion error if the property is specified, but something goes
* wrong
*/
@SuppressWarnings("CallToPrintStackTrace")
static void configureContext(Context ctx) throws AssertionError {
var insightProp = System.getProperty(INSIGHT_PROP);
if (insightProp != null) {
var insightFile = new File(insightProp);
try {
insightFile = insightFile.getCanonicalFile();
assert insightFile.isFile()
: "Cannot find " + insightFile + " specified via " + INSIGHT_PROP + " property";
ACTIVE = new ContextInsightSetup(ctx, insightFile.toPath());
ACTIVE.initialize();
} catch (Error | Exception ex) {
var ae =
new AssertionError(
"Cannot initialize " + insightFile + " specified via " + INSIGHT_PROP + " property",
ex);
ae.printStackTrace();
throw ae;
}
}
}

private void initialize() throws IOException {
var insightCode = Files.readString(insightFile);
var language = "js";
if (insightFile.getFileName().toString().endsWith(".py")) {
language = "python";
}
var insightSrc =
Source.newBuilder("epb", insightFile.toFile())
.content(language + ":0#" + insightCode)
.build();

var instrument = ctx.getEngine().getInstruments().get("insight");
@SuppressWarnings("unchecked")
var insight = (Function<Source, AutoCloseable>) instrument.lookup(Function.class);
insightHandle = insight.apply(insightSrc);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: Level) {
connection
} else null
})
if (System.getProperty("enso.dev.insight") != null) {
stdOut.attach(arr => System.out.write(arr))
}

system.eventStream.setLogLevel(AkkaConverter.toAkka(logLevel))
log.trace("Set akka log level to [{}]", logLevel)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.enso.common.test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import org.enso.common.ContextFactory;
import org.enso.test.utils.ContextUtils;
import org.hamcrest.core.AllOf;
import org.junit.AfterClass;
import org.junit.Test;

/**
* Demonstrates usage of {@code -Denso.dev.insight=insightScript.js} property. This is a
* developement only support for playing with GraalVM Insight scripts inside of the IDE as well in
* CLI.
*/
public class ContextInsightSetupTest {

public ContextInsightSetupTest() {}

@AfterClass
public static void cleanupInsightProperty() {
System.getProperties().remove("enso.dev.insight");
}

@Test
public void initializeInsightViaProperty() throws Exception {
var insight = File.createTempFile("insight", ".js");
try (java.io.FileWriter w = new FileWriter(insight)) {
w.write(
"""
print("Insight started. Properties: " + Object.getOwnPropertyNames(insight).sort());
""");
}

System.setProperty("enso.dev.insight", insight.getPath());

var out = new ByteArrayOutputStream();
try (var ctx = ContextFactory.create().out(out).build()) {

var fourtyTwo = ContextUtils.evalModule(ctx, """
main = 42
""");

assertEquals("42", fourtyTwo.toString());

assertThat(
out.toString(),
AllOf.allOf(
containsString("Insight started."), containsString("Properties: id,version")));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ private int splitAt(CharSequence seq, char ch) {
}
at++;
}
throw new ForeignParsingException("No " + ch + " found", this);
throw new ForeignParsingException(
"No `" + ch + "` found. Expecting `lang:lineno#code` format.", this);
}

@Override
Expand Down Expand Up @@ -106,12 +107,17 @@ yield switch (id) {
private ForeignFunctionCallNode parseJs() {
var context = EpbContext.get(this);
var inner = context.getInnerContext();
var code = foreignSource(langAndCode);
var args = Arrays.stream(argNames).skip(1).collect(Collectors.joining(","));
var wrappedSrc = "var poly_enso_eval=function(" + args + "){" + code + "\n};poly_enso_eval";
Source source = newSource("js", wrappedSrc);
var fn = inner.evalPublic(this, source);
return JsForeignNode.build(fn);
if (inner != null) {
var code = foreignSource(langAndCode);
var args = Arrays.stream(argNames).collect(Collectors.joining(","));
var wrappedSrc = "var poly_enso_eval=function(" + args + "){" + code + "\n};poly_enso_eval";
Source source = newSource("js", wrappedSrc);
var fn = inner.evalPublic(this, source);
return JsForeignNode.build(fn);
} else {
return new GenericForeignNode(
RootNode.createConstantNode("Cannot evaluate script in inner context!").getCallTarget());
}
}

private ForeignFunctionCallNode parseGeneric(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.enso.interpreter.epb;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropException;
Expand Down Expand Up @@ -34,15 +35,15 @@ static JsForeignNode build(Object jsFunction) {
@Specialization
Object doExecute(Object[] arguments, @CachedLibrary("foreignFunction") InteropLibrary iop)
throws InteropException {
var args = new ArgumentsArray(arguments);
var self = arguments[0];
var args = new ArgumentsArray(arguments);
var raw = iop.invokeMember(getForeignFunction(), "apply", self, args);
return coercePrimitiveNode.execute(raw);
}

@ExportLibrary(InteropLibrary.class)
static final class ArgumentsArray implements TruffleObject {
private static final int OFFSET = 1;
private static final int OFFSET = 0;
private final Object[] items;

public ArgumentsArray(Object... items) {
Expand Down Expand Up @@ -88,6 +89,7 @@ boolean isArrayElementInsertable(long index) {
}

@Override
@TruffleBoundary
public String toString() {
return Arrays.toString(items);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.enso.interpreter.epb;

import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import com.oracle.truffle.api.source.Source;
import java.util.Collections;
Expand All @@ -21,7 +22,7 @@ public void sourceWithoutHash() throws Exception {
var res = node.execute(null);
fail("Unexpected result: " + res);
} catch (ForeignParsingException e) {
assertEquals("No # found", e.getMessage());
assertEquals("No `#` found. Expecting `lang:lineno#code` format.", e.getMessage());
}
}
}
13 changes: 11 additions & 2 deletions test/Base_Tests/src/Semantic/Js_Interop_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ foreign js debug = """
type My_Type
Value a b

foreign js my_method self = """
foreign js my_method_self self = """
return self.a + self.b;

foreign js my_method_this self = """
return this.a + this.b;

my_method_2 self x = self.my_method * x
my_method_2 self x = self.my_method_this * x

foreign js my_method_3 self y = """
var r = this.my_method_2(y)
Expand Down Expand Up @@ -91,6 +94,12 @@ add_specs suite_builder = suite_builder.group "Polyglot JS" pending=pending_js_m
group_builder.specify "should allow mutual calling of instance-level methods" <|
My_Type.Value 3 4 . my_method_3 5 . should_equal 36

group_builder.specify "should allows to access this in instance methods" <|
My_Type.Value 3 5 . my_method_this . should_equal 8

group_builder.specify "should allows to access self in instance methods" <|
My_Type.Value 5 4 . my_method_self . should_equal 9

group_builder.specify "should expose methods and fields of JS objects" <|
obj = make_object
obj.x . should_equal 10
Expand Down

0 comments on commit fe45da9

Please sign in to comment.