Skip to content

Commit c8f641e

Browse files
committed
Fix #3 Support SBT
1 parent db5ca43 commit c8f641e

File tree

6 files changed

+245
-111
lines changed

6 files changed

+245
-111
lines changed

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ It's easy when the class loader is of type `URLClassLoader <http://docs.oracle.c
8686
when the process is started by `SBT <http://www.scala-sbt.org/>`_), Scalive must
8787
solve the problem in the "case by case" style.
8888

89-
Currently, Scalive only supports:
89+
Currently, Scalive only supports these process types:
9090

91-
* Single class loader process (normal standalone JVM process, like
91+
* Process with only one class loader (normal standalone JVM process, like
9292
`Play <http://www.playframework.com/>`_ or
9393
`Xitrum <http://ngocdaothanh.github.io/xitrum/>`_).
94-
* SBT.
94+
* Process started by SBT ``run``.
9595

9696
If your process/class loader is not supported, please `create an issue <https://github.com/ngocdaothanh/scalive/issues>`_.
9797

agent/src/main/java/scalive/Agent.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,29 @@
77

88
public class Agent {
99
/**
10-
* @param agentArgs <jarpath> <server port>;
11-
* jarpath: absolute path to directory that contains scalive-agent.jar,
12-
* scala-library.jar, scala-compiler.jar, and scala-reflect.jar
13-
* @throws IOException
10+
* @param agentArgs <jarpath> <server port> [class loader id]
11+
*
12+
* jarpath is the absolute path this directory:
13+
*
14+
* {{{
15+
* jarpath/
16+
* scalive-agent.jar
17+
* scalive-client.jar
18+
* scalive-repl.jar
19+
*
20+
* 2.10.3/
21+
* scala-library.jar
22+
* scala-compiler.jar
23+
* scala-reflect.jar
24+
*
25+
* [Other Scala versions]
26+
* }}}
1427
*/
1528
public static void agentmain(String agentArgs, Instrumentation inst) throws IOException {
1629
final String[] args = agentArgs.split(" ");
1730
final String jarpath = args[0];
1831
final int port = Integer.parseInt(args[1]);
32+
final String clId = (args.length == 3) ? args[2] : null;
1933

2034
System.out.println("[Scalive] REPL server starts at port " + port);
2135
final ServerSocket server = new ServerSocket(port);
@@ -31,7 +45,7 @@ public static void agentmain(String agentArgs, Instrumentation inst) throws IOEx
3145
public void run() {
3246
try {
3347
Socket client = server.accept(); // Block until a connection comes in
34-
Server.serve(client, jarpath);
48+
Server.serve(client, jarpath, clId);
3549
} catch (Exception e) {
3650
e.printStackTrace();
3751
} finally {

agent/src/main/java/scalive/Classpath.java

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,59 @@
66
import java.net.URLClassLoader;
77

88
public class Classpath {
9-
private static Method addURL = getAddURL();
10-
11-
// http://stackoverflow.com/questions/8222976/why-urlclassloader-addurl-protected-in-java
12-
private static Method getAddURL() {
13-
try {
14-
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
15-
method.setAccessible(true);
16-
return method;
17-
} catch (Exception e) {
18-
e.printStackTrace();
19-
return null;
20-
}
21-
}
22-
23-
/**
24-
* @param jarpath Directory containing the jar
25-
*
26-
* @param jarbase Ex: "scalive-agent", not "scalive-agent-1.0.jar"
27-
*/
28-
public static String findJar(String jarpath, String jarbase) throws Exception {
29-
File dir = new File(jarpath);
30-
File[] files = dir.listFiles();
31-
32-
for (int i = 0; i < files.length; i++) {
33-
File file = files[i];
34-
String name = file.getName();
35-
if (file.isFile() && name.endsWith(".jar") && name.startsWith(jarbase))
36-
return file.getPath();
37-
}
38-
throw new Exception("Could not find " + jarbase + " in " + jarpath);
39-
}
40-
41-
public static void addPath(URLClassLoader cl, String path) throws Exception {
42-
URL url = new File(path).toURI().toURL();
43-
addURL.invoke(cl, url);
44-
}
45-
46-
/** Combination of findJar and addPath. */
47-
public static void findAndAddJar(URLClassLoader cl, String jarpath, String jarbase) throws Exception {
48-
String jar = findJar(jarpath, jarbase);
49-
addPath(cl, jar);
50-
}
9+
private static Method addURL = getAddURL();
10+
11+
// http://stackoverflow.com/questions/8222976/why-urlclassloader-addurl-protected-in-java
12+
private static Method getAddURL() {
13+
try {
14+
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
15+
method.setAccessible(true);
16+
return method;
17+
} catch (Exception e) {
18+
e.printStackTrace();
19+
return null;
20+
}
21+
}
22+
23+
//--------------------------------------------------------------------------
24+
25+
/**
26+
* @param jarpath Directory containing the jar
27+
*
28+
* @param jarbase Ex: "scalive-agent", not "scalive-agent-1.0.jar"
29+
*/
30+
public static String findJar(String jarpath, String jarbase) throws Exception {
31+
File dir = new File(jarpath);
32+
File[] files = dir.listFiles();
33+
34+
for (int i = 0; i < files.length; i++) {
35+
File file = files[i];
36+
String name = file.getName();
37+
if (file.isFile() && name.endsWith(".jar") && name.startsWith(jarbase))
38+
return file.getPath();
39+
}
40+
throw new Exception("Could not find " + jarbase + " in " + jarpath);
41+
}
42+
43+
public static void addPath(URLClassLoader cl, String path) throws Exception {
44+
URL url = new File(path).toURI().toURL();
45+
addURL.invoke(cl, url);
46+
}
47+
48+
/** Combination of findJar and addPath. */
49+
public static void findAndAddJar(URLClassLoader cl, String jarpath, String jarbase) throws Exception {
50+
String jar = findJar(jarpath, jarbase);
51+
addPath(cl, jar);
52+
}
53+
54+
public static void addJarToURLClassLoader(
55+
URLClassLoader cl, String jarpath, String jarbase, String representativeClass
56+
) throws Exception {
57+
try {
58+
Class.forName(representativeClass, true, cl);
59+
} catch (ClassNotFoundException e) {
60+
System.out.println("[Scalive] Load " + jarbase);
61+
Classpath.findAndAddJar(cl, jarpath, jarbase);
62+
}
63+
}
5164
}
Lines changed: 129 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
package scalive;
22

3+
import java.io.File;
34
import java.io.InputStream;
45
import java.io.OutputStream;
56
import java.io.PrintStream;
7+
import java.lang.reflect.Field;
68
import java.lang.reflect.Method;
79
import java.net.Socket;
10+
import java.net.URL;
811
import java.net.URLClassLoader;
12+
import java.util.Collection;
13+
14+
import net.djpowell.liverepl.discovery.ClassLoaderInfo;
15+
import net.djpowell.liverepl.discovery.Discovery;
916

1017
public class Server {
18+
// Load this Scala version if Scala has not been loaded in the target process
1119
private static final String DEFAULT_SCALA_VERSION = "2.10.3";
1220

13-
public static void serve(Socket client, String jarpath) throws Exception {
21+
// Use only one instance so that we have a stable list of class loaders
22+
private static final Discovery DISCOVERY = new Discovery();
23+
24+
public static void serve(Socket client, String jarpath, String clId) throws Exception {
1425
InputStream in = client.getInputStream();
1526
OutputStream out = client.getOutputStream();
1627

@@ -23,12 +34,14 @@ public static void serve(Socket client, String jarpath) throws Exception {
2334
System.setErr(new PrintStream(out));
2435

2536
try {
26-
URLClassLoader cl = (URLClassLoader) Server.class.getClassLoader();
27-
addJarsToClassLoader(cl, jarpath);
37+
ClassLoader parentCl = getClassloader(clId);
38+
if (parentCl == null) return;
2839

29-
Class<?> repl = Class.forName("scalive.Repl");
30-
Method method = repl.getMethod("run", URLClassLoader.class, InputStream.class, OutputStream.class);
31-
method.invoke(null, cl, in, out);
40+
URLClassLoader cl = createClassLoaderWithReplJars(jarpath, parentCl);
41+
Class<?> repl = Class.forName("scalive.Repl", true, cl);
42+
Method method = repl.getMethod("run", String.class, InputStream.class, OutputStream.class);
43+
String classpath = getClasspathForRepl(cl, parentCl);
44+
method.invoke(null, classpath, in, out);
3245
} finally {
3346
System.setIn(oldIn);
3447
System.setOut(oldOut);
@@ -38,33 +51,125 @@ public static void serve(Socket client, String jarpath) throws Exception {
3851
}
3952
}
4053

41-
private static void addJarsToClassLoader(URLClassLoader cl, String jarpath) throws Exception {
54+
//--------------------------------------------------------------------------
55+
56+
/**
57+
* @param clId null means no class loader ID is specified; in this case,
58+
* when there's only one class loader, it will be returned
59+
*
60+
* @return null if class loader not found or there are multiple class loaders
61+
* but clId is not specified
62+
*/
63+
private static ClassLoader getClassloader(String clId) {
64+
if (clId == null) {
65+
if (DISCOVERY.listClassLoaders().size() > 1) {
66+
DISCOVERY.dumpList(System.out);
67+
return null;
68+
} else {
69+
clId = "0";
70+
}
71+
}
72+
73+
ClassLoaderInfo cli = DISCOVERY.findClassLoader(clId);
74+
if (cli == null) {
75+
System.out.println("[Scalive] Could not find class loader: " + clId);
76+
return null;
77+
}
78+
79+
return cli.getClassLoader();
80+
}
81+
82+
//--------------------------------------------------------------------------
83+
84+
private static URLClassLoader createClassLoaderWithReplJars(String jarpath, ClassLoader parentCl) throws Exception {
85+
URLClassLoader cl = new URLClassLoader(new URL[] {}, parentCl);
86+
4287
// Try scala-library first
43-
addJarToClassLoader(cl, jarpath + "/" + DEFAULT_SCALA_VERSION, "scala-library", "scala.AnyVal");
88+
Classpath.addJarToURLClassLoader(cl, jarpath + "/" + DEFAULT_SCALA_VERSION, "scala-library", "scala.AnyVal");
4489

4590
// So that we can get the actual Scala version being used
46-
String version = getScalaVersion();
91+
String version = getScalaVersion(cl);
4792

48-
addJarToClassLoader(cl, jarpath + "/" + version, "scala-compiler", "scala.tools.nsc.interpreter.ILoop");
49-
addJarToClassLoader(cl, jarpath + "/" + version, "scala-reflect", "scala.reflect.runtime.JavaUniverse");
93+
Classpath.addJarToURLClassLoader(cl, jarpath + "/" + version, "scala-reflect", "scala.reflect.runtime.JavaUniverse");
94+
Classpath.addJarToURLClassLoader(cl, jarpath + "/" + version, "scala-compiler", "scala.tools.nsc.interpreter.ILoop");
5095

51-
addJarToClassLoader(cl, jarpath, "scalive-repl", "scalive.Repl");
52-
}
96+
Classpath.addJarToURLClassLoader(cl, jarpath, "scalive-repl", "scalive.Repl");
5397

54-
private static void addJarToClassLoader(
55-
URLClassLoader cl, String jarpath, String jarbase, String representativeClass
56-
) throws Exception {
57-
try {
58-
Class.forName(representativeClass);
59-
} catch (ClassNotFoundException e) {
60-
System.out.println("[Scalive] Load " + jarbase);
61-
Classpath.findAndAddJar(cl, jarpath, jarbase);
62-
}
98+
return cl;
6399
}
64100

65-
private static String getScalaVersion() throws Exception {
66-
Class<?> k = Class.forName("scala.util.Properties");
101+
private static String getScalaVersion(ClassLoader cl) throws Exception {
102+
Class<?> k = Class.forName("scala.util.Properties", true, cl);
67103
Method m = k.getDeclaredMethod("versionNumberString");
68104
return (String) m.invoke(k);
69105
}
106+
107+
//--------------------------------------------------------------------------
108+
109+
/** @return The classpath that should be set to the REPL
110+
* @throws Exception */
111+
private static String getClasspathForRepl(URLClassLoader withReplJars, ClassLoader parentCl) throws Exception {
112+
String fromParent = null;
113+
String fromRepl = getURLClasspath(withReplJars);
114+
115+
if (parentCl.getClass().getName().equals("sbt.classpath.ClasspathFilter")) {
116+
fromParent = sbtGetClasspathFromClasspathFilter(withReplJars, parentCl);
117+
} else if (parentCl instanceof URLClassLoader) {
118+
fromParent = getURLClasspath((URLClassLoader) parentCl);
119+
}
120+
121+
return (fromParent == null) ? fromRepl : fromParent + File.pathSeparator + fromRepl;
122+
}
123+
124+
// http://stackoverflow.com/questions/4121567/embedded-scala-repl-inherits-parent-classpath
125+
private static String getURLClasspath(URLClassLoader cl) {
126+
URL[] urls = cl.getURLs();
127+
StringBuffer buf = new StringBuffer();
128+
for (URL url: urls) {
129+
if (buf.length() > 0) buf.append(File.pathSeparator);
130+
buf.append(url);
131+
}
132+
return buf.toString();
133+
}
134+
135+
//--------------------------------------------------------------------------
136+
137+
// http://www.scala-sbt.org/release/api/index.html#sbt.classpath.ClasspathFilter
138+
private static String sbtGetClasspathFromClasspathFilter(URLClassLoader withReplJars, ClassLoader parentCl) throws Exception {
139+
ClassLoader mainClassLoader = sbtFindMainClassLoader();
140+
Class<?> ClasspathFilterClass = Class.forName("sbt.classpath.ClasspathFilter", true, mainClassLoader);
141+
Field classpathField = ClasspathFilterClass.getDeclaredField("classpath");
142+
classpathField.setAccessible(true);
143+
144+
Object set = classpathField.get(parentCl);
145+
Class<?> setClass = Class.forName("scala.collection.TraversableOnce", true, withReplJars);
146+
Method mkString = setClass.getDeclaredMethod("mkString", String.class);
147+
return (String) mkString.invoke(set, File.pathSeparator);
148+
}
149+
150+
private static ClassLoader sbtFindMainClassLoader() throws Exception {
151+
// System Class Loader:
152+
//
153+
// #Id ClassLoader Info
154+
// 0 AppClassLoader <system>
155+
//
156+
// Thread Context Class Loaders:
157+
//
158+
// #Id ClassLoader Info
159+
// 1 URLClassLoader main #1 [main] <--- Take out this one
160+
// 2 ClasspathFilter xitrum-akka.actor.default-dispatcher-2 #31 [run-main-group-0]
161+
162+
Collection<ClassLoaderInfo> clis = DISCOVERY.listClassLoaders();
163+
for (ClassLoaderInfo cli: clis) {
164+
ClassLoader cl = cli.getClassLoader();
165+
String name = cli.getClassLoaderName();
166+
167+
// Can't use info because it can be "process reaper #8 [system]" etc.
168+
boolean mainClassLoader = cl instanceof URLClassLoader && "URLClassLoader".equals(name);
169+
170+
if (mainClassLoader) return cl;
171+
}
172+
173+
throw new Exception("[Scalive] SBT main class loader not found");
174+
}
70175
}

client/src/main/scala/scalive/AgentLoader.scala

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,23 @@ import java.net.URLClassLoader
88

99
object AgentLoader {
1010
/**
11-
* @param args <jarpath> [class loader id];
12-
* jarpath: absolute path to directory that contains scalive-agent.jar,
13-
* scala-library.jar, scala-compiler.jar, and scala-reflect.jar
11+
* @param args <jarpath> [class loader id]
12+
*
13+
* jarpath is the absolute path this directory:
14+
*
15+
* {{{
16+
* jarpath/
17+
* scalive-agent.jar
18+
* scalive-client.jar
19+
* scalive-repl.jar
20+
*
21+
* 2.10.3/
22+
* scala-library.jar
23+
* scala-compiler.jar
24+
* scala-reflect.jar
25+
*
26+
* [Other Scala versions]
27+
* }}}
1428
*/
1529
def main(args: Array[String]) {
1630
if (args.length != 1 && args.length != 2 && args.length != 3) {

0 commit comments

Comments
 (0)