The program below behaves differently when run under sbt than using
plain java, and I can't figure out why. Can anyone else?
It's supposed to reload Foo.class every ten seconds and call toString
on an object of type Foo. The idea is that if you change the source
of Foo.toString, and recompile, the running program should start
printing the new value. (Because we use a new classloader, the new
object of type Foo is not the same type as the previous one, so it can
contain a different definition of toString.) So initially Foo looks
like this:
class Foo {
override def toString = "hello"
}
and then you change it to
class Foo {
override def toString = "goodbye"
}
and recompile. (You have to time this carefully, as it has to finish
within 10 seconds, but that's easy via sbt ~compile, which is quite
fast.)
When running under sbt 0.11.2, it behaves as expected:
$ sbt run [info] Set current project to Hot Reload Example (in build
file:/home/srhea/src/hotreload/)
[info] Running HotReload
hello
hello
goodbye
goodbye
...
But when I run it using plain old java, it doesn't change after a recompile:
srhea@rack22:hotreload:$ java -cp
/home/srhea/src/hotreload/target/scala-2.9.1/classes/:/home/srhea/.sbt/boot/scala-2.9.1/lib/scala-library.jar
HotReload
hello
hello
hello
...
I know sbt plays fancy tricks with the classloader. Do those tricks
explain this behavior?
I'm using Sun Java 1.6.0_26 on Debian Linux. My build.sbt and sbt
script are also attached.
Sean
--
Construct the world in which you want to live. Then live there.
// HotReload.scala follows:
import scala.collection.mutable.HashMap
import java.net.URLClassLoader
import java.io.File
// Run this class, then change Foo.toString to print something else
and recompile.
// The still-running program will load the new Foo class and print
your new string.
class Foo {
override def toString = "hello"
}
object HotReload {
val classpath = Array(
"target/scala-2.9.1/classes/",
System.getProperty("user.home") +
"/.sbt/boot/scala-2.9.1/lib/scala-library.jar"
).map { s =>
val f = new File(s)
"file://" + f.getAbsolutePath + (if (f.isDirectory) "/" else "")
}
def main(args: Array[String]) {
while (true) {
val cl = new URLClassLoader(classpath.map(new java.net.URL(_)))
val ctor = cl.loadClass("Foo").getConstructors.head
val obj = ctor.newInstance()
println(obj.toString)
Thread.sleep(10000);
}
}
}
// build.sbt follows:
name := "Hot Reload Example"
version := "0.1-SNAPSHOT"
scalaVersion := "2.9.1"
// sbt script follows:
#!/bin/sh
#
# Copyright (c) 2010 Jon Buffington. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Is the location of the SBT launcher JAR file.
LAUNCHJAR="/usr/share/java/sbt-launch-0.11.2.jar"
# Ensure enough heap space is created for SBT.
if [ -z "$JAVA_OPTS" ]; then
JAVA_OPTS="-Xmx512M"
fi
# Assume java is already in the shell path.
exec java $JAVA_OPTS -jar "$LAUNCHJAR" "$@"
Digging deeper, it seems that Scala inserts it's own classloader in
the hierarchy, confusingly also named URLClassLoader
(scala.tools.nsc.util.ScalaClassLoader$URLClassLoader, not
java.net.URLClassLoader).
Harold
> --
> You received this message because you are subscribed to the Google Groups "Bay Area Scala Enthusiasts" group.
> To post to this group, send email to scala...@googlegroups.com.
> To unsubscribe from this group, send email to scala-base+...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/scala-base?hl=en.
>