diff --git a/src/main/scala/sbt/DefaultProject.scala b/src/main/scala/sbt/DefaultProject.scala index dc65ce101..f5d62213d 100644 --- a/src/main/scala/sbt/DefaultProject.scala +++ b/src/main/scala/sbt/DefaultProject.scala @@ -75,7 +75,6 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec /** The options provided to the 'doc' and 'docTest' actions.*/ def documentOptions: Seq[ScaladocOption] = - LinkSource :: documentTitle(name + " " + version + " API") :: windowTitle(name + " " + version + " API") :: Nil @@ -404,6 +403,7 @@ abstract class BasicWebScalaProject extends BasicScalaProject with WebScalaProje /** The port that Jetty runs on. */ def jettyPort: Int = JettyRunner.DefaultPort + lazy val jettyReload = task { jettyInstance.reload(); None } describedAs(JettyReloadDescription) lazy val jettyRestart = jettyStop && jettyRun lazy val jettyStop = jettyStopAction protected def jettyStopAction = jettyStopTask(jettyInstance) describedAs(JettyStopDescription) @@ -489,6 +489,8 @@ object BasicWebScalaProject "Starts the Jetty server and serves this project as a web application." val JettyDescription = "Starts the Jetty server and serves this project as a web application. Waits until interrupted, so it is suitable to call this batch-style." + val JettyReloadDescription = + "Forces a reload of a web application running in a Jetty server started by 'jetty-run'. Does nothing if Jetty is not running." } /** Analyzes the dependencies of a project after compilation. All methods except `snapshot` return a * `PathFinder`. The underlying calculations are repeated for each call to PathFinder.get. */ diff --git a/src/main/scala/sbt/WebApp.scala b/src/main/scala/sbt/WebApp.scala index d8b748b99..b1d8404dc 100644 --- a/src/main/scala/sbt/WebApp.scala +++ b/src/main/scala/sbt/WebApp.scala @@ -25,6 +25,7 @@ class JettyRunner(configuration: JettyConfiguration) extends ExitHook running.foreach(_.stop()) running = None } + def reload() = running.foreach(_.reload()) def apply(): Option[String] = { import configuration._ @@ -32,10 +33,17 @@ class JettyRunner(configuration: JettyConfiguration) extends ExitHook { val baseLoader = this.getClass.getClassLoader val classpathURLs = jettyClasspath.get.map(_.asURL).toSeq - val loader: ClassLoader = new java.net.URLClassLoader(classpathURLs.toArray, baseLoader) - val lazyLoader = new LazyFrameworkLoader(implClassName, Array(FileUtilities.classLocation[Stoppable].toURI.toURL), loader, baseLoader) + val jettyParentLoader = configuration match { case d: DefaultJettyConfiguration => d.parentLoader; case _ => ClassLoader.getSystemClassLoader } + val jettyLoader: ClassLoader = new java.net.URLClassLoader(classpathURLs.toArray, jettyParentLoader) + + val jettyFilter = (name: String) => name.startsWith("org.mortbay.") + val notJettyFilter = (name: String) => !jettyFilter(name) + + val dual = new xsbt.DualLoader(baseLoader, notJettyFilter, x => true, jettyLoader, jettyFilter, x => false) + + val lazyLoader = new LazyFrameworkLoader(implClassName, Array(FileUtilities.classLocation[Stoppable].toURI.toURL), dual, baseLoader) val runner = ModuleUtilities.getObject(implClassName, lazyLoader).asInstanceOf[JettyRun] - runner(configuration) + runner(configuration, jettyLoader) } if(running.isDefined) @@ -66,10 +74,11 @@ class JettyRunner(configuration: JettyConfiguration) extends ExitHook private trait Stoppable { def stop(): Unit + def reload(): Unit } private trait JettyRun { - def apply(configuration: JettyConfiguration): Stoppable + def apply(configuration: JettyConfiguration, jettyLoader: ClassLoader): Stoppable } sealed trait JettyConfiguration extends NotNull { @@ -105,7 +114,7 @@ private object LazyJettyRun extends JettyRun { import org.mortbay.jetty.{Handler, Server} import org.mortbay.jetty.nio.SelectChannelConnector - import org.mortbay.jetty.webapp.WebAppContext + import org.mortbay.jetty.webapp.{WebAppClassLoader, WebAppContext} import org.mortbay.log.Log import org.mortbay.util.Scanner import org.mortbay.xml.XmlConfiguration @@ -114,7 +123,7 @@ private object LazyJettyRun extends JettyRun val DefaultMaxIdleTime = 30000 - def apply(configuration: JettyConfiguration): Stoppable = + def apply(configuration: JettyConfiguration, jettyLoader: ClassLoader): Stoppable = { val oldLog = Log.getLog Log.setLog(new JettyLogger(configuration.log)) @@ -127,15 +136,17 @@ private object LazyJettyRun extends JettyRun import c._ configureDefaultConnector(server, port) def classpathURLs = classpath.get.map(_.asURL).toSeq - def createLoader = new URLClassLoader(classpathURLs.toArray, parentLoader) val webapp = new WebAppContext(war.absolutePath, contextPath) - webapp.setClassLoader(createLoader) + + def createLoader = new WebAppClassLoader(jettyLoader, webapp) { override def getURLs = classpathURLs.toArray } + def setLoader() = webapp.setClassLoader(createLoader) + + setLoader() server.setHandler(webapp) - Some(new Scanner.BulkListener { - def filesChanged(files: java.util.List[_]) { - reload(server, webapp.setClassLoader(createLoader), log) - } + Some(new Scanner.BulkListener with Reload { + def reloadApp() = reload(server, setLoader(), log) + def filesChanged(files: java.util.List[_]) { reloadApp() } }) case c: CustomJettyConfiguration => for(x <- c.jettyConfigurationXML) @@ -169,7 +180,7 @@ private object LazyJettyRun extends JettyRun try { server.start() - new StopServer(new WeakReference(server), configureScanner(), oldLog) + new StopServer(new WeakReference(server), listener.map(new WeakReference(_)), configureScanner(), oldLog) } catch { case e => server.stop(); throw e } } @@ -180,19 +191,16 @@ private object LazyJettyRun extends JettyRun defaultConnector.setMaxIdleTime(DefaultMaxIdleTime) server.addConnector(defaultConnector) } - private class StopServer(serverReference: Reference[Server], scannerReferenceOpt: Option[Reference[Scanner]], oldLog: org.mortbay.log.Logger) extends Stoppable + trait Reload { def reloadApp(): Unit } + private class StopServer(serverReference: Reference[Server], reloadReference: Option[Reference[Reload]], scannerReferenceOpt: Option[Reference[Scanner]], oldLog: org.mortbay.log.Logger) extends Stoppable { + def reload(): Unit = on(reloadReference)(_.reloadApp()) + private def on[T](refOpt: Option[Reference[T]])(f: T => Unit): Unit = refOpt.foreach(ref => onReferenced(ref.get)(f)) + private def onReferenced[T](t: T)(f: T => Unit): Unit = if(t == null) () else f(t) def stop() { - val server = serverReference.get - if(server != null) - server.stop() - for(scannerReference <- scannerReferenceOpt) - { - val scanner = scannerReference.get - if(scanner != null) - scanner.stop() - } + onReferenced(serverReference.get)(_.stop()) + on(scannerReferenceOpt)(_.stop()) Log.setLog(oldLog) } } diff --git a/src/sbt-test/dependency-management/package-to-publish/project/build.properties b/src/sbt-test/dependency-management/package-to-publish/project/build.properties new file mode 100644 index 000000000..5a5bc0dec --- /dev/null +++ b/src/sbt-test/dependency-management/package-to-publish/project/build.properties @@ -0,0 +1,2 @@ +project.name=Publish Test +project.version=1.0 \ No newline at end of file diff --git a/src/sbt-test/dependency-management/package-to-publish/project/build/Test.scala b/src/sbt-test/dependency-management/package-to-publish/project/build/Test.scala new file mode 100644 index 000000000..f7a1dd0f2 --- /dev/null +++ b/src/sbt-test/dependency-management/package-to-publish/project/build/Test.scala @@ -0,0 +1,34 @@ +import sbt._ + +class Test(info: ProjectInfo) extends DefaultProject(info) +{ + def ivyCacheDirectory = outputPath / "ivy-cache" + override def updateOptions = CacheDirectory(ivyCacheDirectory) :: super.updateOptions.toList + + override def managedStyle = ManagedStyle.Maven + def testRepoPath = path("test-repo") + val publishTo = Resolver.file("test repo", testRepoPath asFile) + + def srcExt = "-sources.jar" + def srcFilter = extFilter(srcExt) + def docExt = "-javadoc.jar" + def docFilter = extFilter(docExt) + def extFilter(ext: String) = "*" + ext + + override def packageDocsJar = defaultJarPath(docExt) + override def packageSrcJar= defaultJarPath(srcExt) + + val sourceArtifact = Artifact(artifactID, "src", "jar", "sources") + val docsArtifact = Artifact(artifactID, "docs", "jar", Some("javadoc"), Nil, None) + override def packageToPublishActions = super.packageToPublishActions ++ Seq(packageDocs, packageSrc) + + lazy val check = task { check0 } + + def check0 = checkPom orElse checkBin orElse checkSource orElse checkDoc + def checkPom = exists("pom", "*.pom") + def checkDoc = exists("javadoc", docFilter) + def checkSource = exists("sources", srcFilter) + def checkBin = exists("binary", "*.jar" - (srcFilter | docFilter)) + def exists(label: String, filter: sbt.NameFilter) = + if( (testRepoPath ** filter).get.isEmpty) Some("No " + label + " published") else None +} \ No newline at end of file diff --git a/src/sbt-test/dependency-management/package-to-publish/src/main/scala/Test.scala b/src/sbt-test/dependency-management/package-to-publish/src/main/scala/Test.scala new file mode 100644 index 000000000..7396cec1d --- /dev/null +++ b/src/sbt-test/dependency-management/package-to-publish/src/main/scala/Test.scala @@ -0,0 +1 @@ +class TestClass \ No newline at end of file diff --git a/src/sbt-test/dependency-management/package-to-publish/test b/src/sbt-test/dependency-management/package-to-publish/test new file mode 100644 index 000000000..6bae15e55 --- /dev/null +++ b/src/sbt-test/dependency-management/package-to-publish/test @@ -0,0 +1,3 @@ +-> check +> publish +> check \ No newline at end of file diff --git a/src/sbt-test/web/jsp/changes/index.jsp b/src/sbt-test/web/jsp/changes/index.jsp new file mode 100644 index 000000000..2a7d1f317 --- /dev/null +++ b/src/sbt-test/web/jsp/changes/index.jsp @@ -0,0 +1,5 @@ + +
+ <% out.println(" Hello World!"); %> + + \ No newline at end of file diff --git a/src/sbt-test/web/jsp/project/build/JSP.scala b/src/sbt-test/web/jsp/project/build/JSP.scala index 48aa9c5fd..b248512f8 100644 --- a/src/sbt-test/web/jsp/project/build/JSP.scala +++ b/src/sbt-test/web/jsp/project/build/JSP.scala @@ -9,20 +9,12 @@ class JSP(info: ProjectInfo) extends DefaultWebProject(info) def indexFile = new java.io.File("index.html") import Process._ lazy val getPage = execTask { indexURL #> indexFile } - lazy val checkPage = task { checkHelloWorld() } dependsOn getPage - private def checkHelloWorld() = + lazy val checkPage = task { args => task { checkHelloWorld(args.mkString(" ")) } dependsOn getPage } + + private def checkHelloWorld(checkString: String) = { - try - { - FileUtilities.readString(indexFile, log) match - { - case Right(value) => - if(value.contains("Hello World!")) None - else Some("index.html did not contain 'Hello World!' :\n" +value) - case Left(msg) => Some(msg) - } - } - finally { jettyInstance.stop() } + val value = xsbt.FileUtilities.read(indexFile) + if(value.contains(checkString)) None else Some("index.html did not contain '" + checkString + "' :\n" +value) } } \ No newline at end of file diff --git a/src/sbt-test/web/jsp/test b/src/sbt-test/web/jsp/test index 0ea28e8a8..04a09d4e2 100644 --- a/src/sbt-test/web/jsp/test +++ b/src/sbt-test/web/jsp/test @@ -1,4 +1,12 @@ > update > jetty-run -> check-page -> jetty-stop \ No newline at end of file +> check-page "Hello World!" + +$ copy-file changes/index.jsp src/main/webapp/index.jsp + +> prepare-webapp +> jetty-reload +> check-page "Hello World 2!" + +> jetty-stop +-> check-page "Hello World 2!" \ No newline at end of file diff --git a/src/sbt-test/web/servlet/changes/MyServlet.scala b/src/sbt-test/web/servlet/changes/MyServlet.scala new file mode 100644 index 000000000..15dc6f7eb --- /dev/null +++ b/src/sbt-test/web/servlet/changes/MyServlet.scala @@ -0,0 +1,20 @@ +package test + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +class MyServlet extends HttpServlet { + + val html = +