Skip to content

Commit da4ce87

Browse files
author
Mike Hearn
committed
Various bug fixes and improvements. -jar works now.
Also change how we pass the boot script to Node, should fix #3
1 parent 54744fb commit da4ce87

File tree

7 files changed

+113
-48
lines changed

7 files changed

+113
-48
lines changed

dat-sample/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ plugins {
44
java
55
kotlin("jvm")
66
application
7+
id("com.github.johnrengelman.shadow") version("5.1.0")
78
}
89

910
group = "net.plan99.nodejs"

dat-sample/src/main/kotlin/net/plan99/nodejs/sample/DatExplorer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ fun main() {
3939
""")
4040
}
4141

42-
Thread.sleep(5000)
42+
Thread.sleep(25000)
4343
}

docsite/docs/running-polyglot-programs.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,26 @@ Start your Java programs as normal but run `nodejvm` instead of `java`, e.g.
88

99
`nodejvm -cp "libs/*.jar" my.main.Class arg1 arg2`
1010

11+
## Running the samples
12+
13+
Check out the NodeJVM repository. Then try:
14+
15+
```
16+
gradle dat-sample:run
17+
```
18+
19+
It should join the DAT network and might print some peer infos, depending on your luck.
20+
21+
Also try something a bit less Gradley:
22+
23+
```
24+
gradle build spinners-sample:shadowJar
25+
../build/nodejvm/nodejvm -jar build/libs/spinners-sample-*-all.jar
26+
```
27+
1128
## From Gradle
1229

13-
This is currently a bit awkward. The easiest way is to adjust your JavaCompile tasks to run `nodejvm` instead of `java`:
30+
The easiest way is to adjust your JavaCompile tasks to run `nodejvm` instead of `java`:
1431

1532
```
1633
tasks.withType<JavaExec> {

spinners-sample/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ plugins {
44
java
55
kotlin("jvm")
66
application
7+
id("com.github.johnrengelman.shadow") version("5.1.0")
78
}
89

910
group = "net.plan99.nodejs"

src/main/java/net/plan99/nodejs/java/NodeJS.java

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,28 @@
77
import org.intellij.lang.annotations.Language;
88
import org.jetbrains.annotations.NotNull;
99

10+
import java.io.File;
11+
import java.io.FileInputStream;
12+
import java.io.IOException;
13+
import java.io.InputStream;
1014
import java.lang.reflect.InvocationTargetException;
1115
import java.lang.reflect.Method;
1216
import java.lang.reflect.Modifier;
17+
import java.net.JarURLConnection;
18+
import java.net.MalformedURLException;
19+
import java.net.URL;
20+
import java.net.URLClassLoader;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.nio.file.Paths;
24+
import java.nio.file.StandardOpenOption;
25+
import java.util.Arrays;
1326
import java.util.concurrent.CompletableFuture;
1427
import java.util.concurrent.ExecutionException;
1528
import java.util.concurrent.Executor;
1629
import java.util.concurrent.LinkedBlockingDeque;
1730
import java.util.function.Supplier;
31+
import java.util.jar.JarInputStream;
1832

1933
/**
2034
* Provides an interface to the NodeJS runtime for Java developers. You can only access the NodeJS world
@@ -44,27 +58,72 @@ private static class Linkage {
4458
// Called from the boot.js file as part of NodeJVM startup, do not call.
4559
@SuppressWarnings("unused")
4660
@Deprecated
47-
public static void boot(String entryPointName,
48-
LinkedBlockingDeque<Runnable> taskQueue,
61+
public static void boot(LinkedBlockingDeque<Runnable> taskQueue,
4962
Value evalFunction,
50-
String[] args) {
63+
String[] args) throws ClassNotFoundException, IOException {
64+
try {
65+
boot1(taskQueue, evalFunction, args);
66+
} catch (Throwable e) {
67+
e.printStackTrace();
68+
System.exit(1);
69+
}
70+
}
71+
72+
private static void boot1(LinkedBlockingDeque<Runnable> taskQueue, Value evalFunction, String[] args) throws ClassNotFoundException, IOException {
5173
assert linkage == null : "Don't call this function directly. Already started!";
5274
assert evalFunction.canExecute();
5375
NodeJS.linkage = new Linkage(taskQueue, evalFunction);
5476
Thread.currentThread().setName("NodeJS main thread");
77+
78+
if (args.length == 0) {
79+
System.err.println("You must specify at least a class name, or -jar jarname.jar");
80+
System.exit(1);
81+
} else if (!args[0].equals("-jar")) {
82+
Class<?> entryPoint = Class.forName(args[0]);
83+
startJavaThread(entryPoint, Arrays.copyOfRange(args, 1, args.length));
84+
} else {
85+
File myJar = new File(args[1]);
86+
final URL url = myJar.toURI().toURL();
87+
String mainClassName = "";
88+
try (InputStream stream = Files.newInputStream(Paths.get(args[1]), StandardOpenOption.READ)) {
89+
JarInputStream jis = new JarInputStream(stream);
90+
mainClassName = jis.getManifest().getMainAttributes().getValue("Main-Class");
91+
}
92+
93+
if (mainClassName == null) {
94+
System.err.println("JAR file does not have a Main-Class attribute, is not executable.");
95+
System.exit(1);
96+
}
97+
// Use the parent classloader to forcibly toss out this version of the interop JAR, to avoid confusion
98+
// later when there are two classloaders in play, BUT, holepunch this specific class and inner classes
99+
// through so the state linked up from the bootstrap script is still here. In other words, this class is
100+
// special, so don't give it dependencies outside the JDK.
101+
ClassLoader thisClassLoader = NodeJS.class.getClassLoader();
102+
URLClassLoader child = new URLClassLoader(new URL[] {url}, thisClassLoader.getParent()) {
103+
@Override
104+
protected Class<?> findClass(String name) throws ClassNotFoundException {
105+
if (name.startsWith(NodeJS.class.getName())) {
106+
return thisClassLoader.loadClass(name);
107+
} else
108+
return super.findClass(name);
109+
}
110+
};
111+
Class<?> entryPoint = Class.forName(mainClassName, true, child);
112+
startJavaThread(entryPoint, Arrays.copyOfRange(args, 2, args.length));
113+
}
114+
}
115+
116+
private static void startJavaThread(Class<?> entryPoint, String[] args) {
55117
Thread javaThread = new Thread(() -> {
56118
try {
57-
Class<?> entryPoint = Class.forName(entryPointName);
58119
Method main = entryPoint.getMethod("main", String[].class);
59120
assert Modifier.isStatic(main.getModifiers());
60121
main.invoke(null, new Object[] { args });
61122
System.exit(0);
62123
} catch (NoSuchMethodException e) {
63-
System.err.println("No main method found in " + entryPointName);
124+
System.err.println("No main method found in " + entryPoint.getName());
64125
} catch (InvocationTargetException e) {
65126
e.getCause().printStackTrace();
66-
} catch (ClassNotFoundException e) {
67-
System.err.println(String.format("Main class with name '%s' not found.", entryPointName));
68127
} catch (Throwable e) {
69128
e.printStackTrace();
70129
}

src/main/resources/boot.js

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
const javaEntryPoint = process.argv[2];
2-
3-
if (javaEntryPoint === undefined) {
4-
console.error("You must provide the main class name as the first argument.");
5-
process.exit(1);
6-
}
7-
81
// Set up a task queue that we'll proxy onto the NodeJS main thread.
92
//
103
// We have to do it this way because NodeJS/V8 are not thread safe,
@@ -37,11 +30,6 @@ worker.on('message', (callback) => {
3730
}
3831
});
3932

40-
// We need this wrapper because GraalJS barfs if we try to call eval() directly from Java context, it assumes
33+
// We need this wrapper around eval because GraalJS barfs if we try to call eval() directly from Java context, it assumes
4134
// it will only ever be called from JS context.
42-
let evalWrapper = function(str) {
43-
return eval(str);
44-
};
45-
46-
// Now pass control to the Java side.
47-
Java.type('net.plan99.nodejs.java.NodeJS').boot(javaEntryPoint, javaToJSQueue, evalWrapper, process.argv.slice(2));
35+
Java.type('net.plan99.nodejs.java.NodeJS').boot(javaToJSQueue, function(str) { return eval(str); }, process.env['ARGS'].split(" "));

src/main/resources/nodejvm

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,44 +39,43 @@ fi
3939
# Mangle the command line arguments. We expect to receive a normal JVM command line
4040
# and want to convert it into a GraalJS node command line, which requires adding flags,
4141
# editing flag names, inserting options, etc.
42-
newargs=()
43-
i=0
44-
modifying=true
42+
export NODE_JVM_OPTIONS=""
4543
declare -a args
4644
args=( $@ )
4745
argCount=${#args[@]}
48-
while (( j < argCount )); do
49-
arg="${args[j]}"
50-
newarg="$arg"
46+
i=0
47+
while (( i < argCount )); do
48+
arg="${args[i]}"
5149
case "$arg" in
52-
--classpath|-cp|-classpath)
53-
(( j++ ))
54-
newargs[i]="--vm.cp=$interopJar:${args[j]}"
55-
(( j++ ))
56-
(( i++ ))
57-
continue
58-
;;
59-
--*) if $modifying; then newarg="--vm.${arg[@]:2}"; fi;;
60-
-*) if $modifying; then newarg="--vm.${arg[@]:1}"; fi;;
61-
*) if $modifying; then
62-
modifying=false;
63-
newarg="/dev/stdin $newarg";
64-
fi;;
50+
-cp|-classpath|--classpath)
51+
NODE_JVM_CLASSPATH="${args[i+1]}"
52+
(( i++ ))
53+
;;
54+
--*|-*)
55+
if [[ "$arg" == "-jar" ]]; then break; fi;
56+
NODE_JVM_OPTIONS="$NODE_JVM_OPTIONS $arg"
57+
;;
58+
*)
59+
break;;
6560
esac
66-
newargs[i]="$newarg"
6761
(( i++ ))
68-
(( j++ ))
6962
done
63+
args=${args[@]:$i}
7064

7165
# Make node look for modules in the executed-from directory in the same way
7266
# it would if run from the command line normally.
7367
export NODE_PATH=$PWD/node_modules
7468

75-
# Now feed the boot.js script to the node binary. The quoted "EOF" here
69+
# Now feed the boot.js script to the node binary. The quoted 'EOF' here
7670
# doc string disables parsing and interpolation of the embedded script,
7771
# which is needed because backticks are meaningful to both JS and shell.
78-
cmd="$node --experimental-worker --jvm ${newargs[@]}"
79-
echo $cmd
80-
cat <<"EOF" | $cmd
72+
read -r -d '' BOOTJS <<'EOF'
8173
@bootjs@
8274
EOF
75+
76+
export BOOTJS="$BOOTJS"
77+
export ARGS="$args"
78+
export NODE_JVM_CLASSPATH="$interopJar:$NODE_JVM_CLASSPATH"
79+
80+
cmd="$node --experimental-worker --jvm -e eval(process.env['BOOTJS'])"
81+
exec $cmd

0 commit comments

Comments
 (0)