Skip to content

Commit

Permalink
Misc cleanup for usability (#956)
Browse files Browse the repository at this point in the history
* Copy edit comments
* Add non-compiled classpath resources to the scripted tests
* Move `War / forkOptions` out of config to `warForkOptions`
* Add some missing type annotations
* Re-scope some settings helpers
  • Loading branch information
earldouglas authored Oct 2, 2024
1 parent 7f078cb commit e9256ad
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 115 deletions.
5 changes: 5 additions & 0 deletions src/main/scala/com/earldouglas/sbt/war/SbtWar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package com.earldouglas.sbt.war
import sbt.AutoPlugin
import sbt.Plugins

/** The top-level plugin to be used by default. From the required
* plugins, this brings in all of the webapp components mappings, .war
* file packaging, and mechanisms for running both raw webapp
* components and a packaged .war file.
*/
object SbtWar extends AutoPlugin {

override val requires: Plugins =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ object WarPackagePlugin extends AutoPlugin {

override lazy val projectSettings: Seq[Setting[_]] = {

// Flip webappContents around from (dst -> src) to (src -> dst)
val packageContents: Initialize[Task[Seq[(java.io.File, String)]]] =
WebappComponentsPlugin.webappContents
.map(_.toSeq.map({ case (k, v) => (v, k) }))
.map(_.map(_.swap).toSeq)

val packageTaskSettings: Seq[Setting[_]] =
Defaults.packageTaskSettings(pkg, packageContents)
Expand Down
111 changes: 58 additions & 53 deletions src/main/scala/com/earldouglas/sbt/war/WarPackageRunnerPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.util.concurrent.atomic.AtomicReference
import scala.sys.process.{Process => ScalaProcess}

/** Launches the .war file managed by WarPackagePlugin. Uses a forked
* JVM to run Tomcat via webapp-runner.
* JVM to run Tomcat via com.heroku:webapp-runner.
*/
object WarPackageRunnerPlugin extends AutoPlugin {

Expand All @@ -20,6 +20,8 @@ object WarPackageRunnerPlugin extends AutoPlugin {
lazy val warStart = taskKey[Unit]("start war container")
lazy val warJoin = taskKey[Unit]("join war container")
lazy val warStop = taskKey[Unit]("stop war container")
lazy val warForkOptions =
settingKey[ForkOptions]("war container fork options")
}

import autoImport._
Expand All @@ -28,76 +30,79 @@ object WarPackageRunnerPlugin extends AutoPlugin {
override val requires: Plugins =
WarPackagePlugin && WebappRunnerPlugin

override val projectConfigurations: Seq[Configuration] = Seq(War)
override val projectConfigurations: Seq[Configuration] =
Seq(War)

private lazy val containerInstance =
new AtomicReference[Option[ScalaProcess]](None)

private val startWar: Initialize[Task[Unit]] =
Def.task {
stopContainerInstance()

val runners: Seq[File] =
Classpaths
.managedJars(War, classpathTypes.value, update.value)
.map(_.data)
.toList

runners match {
case runner :: Nil =>
streams.value.log.info("[sbt-war] Starting server")
val process: ScalaProcess =
Fork.java.fork(
(War / forkOptions).value,
Seq(
"-jar",
runner.file.getPath(),
"--port",
warPort.value.toString(),
pkg.value.getPath()
override val projectSettings: Seq[Setting[_]] = {

def stopContainerInstance(): Unit = {
val oldProcess = containerInstance.getAndSet(None)
oldProcess.foreach(_.destroy())
}

val startWar: Initialize[Task[Unit]] =
Def.task {
stopContainerInstance()

val runners: Seq[File] =
Classpaths
.managedJars(War, classpathTypes.value, update.value)
.map(_.data)
.toList

runners match {
case runner :: Nil =>
streams.value.log.info("[sbt-war] Starting server")
val process: ScalaProcess =
Fork.java.fork(
warForkOptions.value,
Seq(
"-jar",
runner.file.getPath(),
"--port",
warPort.value.toString(),
pkg.value.getPath()
)
)
containerInstance.set(Some(process))
case _ :: _ =>
streams.value.log.error(
s"""[sbt-war] Expected one runner, but found ${runners.length}: ${runners
.mkString("\n * ", " * ", "")}"""
)
containerInstance.set(Some(process))
case _ :: _ =>
streams.value.log.error(
s"""[sbt-war] Expected one runner, but found ${runners.length}: ${runners
.mkString("\n * ", " * ", "")}"""
)
case _ =>
streams.value.log.error(
"""[sbt-war] Expected one runner, but found none"""
)
case _ =>
streams.value.log.error(
"""[sbt-war] Expected a runner, but found none"""
)
}
}
}

private val joinWar: Initialize[Task[Unit]] =
Def.task(containerInstance.get.map(_.exitValue))
val joinWar: Initialize[Task[Unit]] =
Def.task(containerInstance.get.map(_.exitValue))

private def stopContainerInstance(): Unit = {
val oldProcess = containerInstance.getAndSet(None)
oldProcess.foreach(_.destroy())
}

private val stopWar: Initialize[Task[Unit]] =
Def.task(stopContainerInstance())
val stopWar: Initialize[Task[Unit]] =
Def.task(stopContainerInstance())

private val onLoadSetting: Initialize[State => State] =
Def.setting {
(Global / onLoad).value
.compose { state: State =>
state.addExitHook(stopContainerInstance())
}
}
val onLoadSetting: Initialize[State => State] =
Def.setting {
(Global / onLoad).value
.compose { state: State =>
state.addExitHook(stopContainerInstance())
}
}

override lazy val projectSettings =
Seq(
warPort := 8080,
warStart := startWar.value,
warJoin := joinWar.value,
warStop := stopWar.value,
War / forkOptions := ForkOptions(),
warForkOptions := ForkOptions(),
Global / onLoad := onLoadSetting.value,
libraryDependencies +=
("com.heroku" % "webapp-runner" % webappRunnerVersion.value intransitive ()) % War
)
}
}
53 changes: 34 additions & 19 deletions src/main/scala/com/earldouglas/sbt/war/WebappComponentsPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,68 @@ import sbt._
/** Identifies the files that compose the webapp (resources, .class
* files, and .jar files). This is used by user-facing plugins
* (WarPlugin and WebappRunnerPlugin).
*
* Webapp components are managed as three sets of mappings:
*
* - webappResources: All the static HTML, CSS, JS, images, etc.
* files to be served by the application. Also, optionally, the
* WEB-INF/web.xml deployment descriptor.
* - webappClasses: All of the classes, etc. on the classpath to be
* copied into the WEB-INF/classes directory.
* - webappLib: All of the .jar files to be copied into the
* WEB-INF/lib directory.
*
* These mappings each have the type Map[String, File], where the key
* is the relative path within the .war file (e.g.
* WEB-INF/classes/Foo.class), and the value is the location of the
* file to be copied there (e.g. target/classes/Foo.class).
*/
object WebappComponentsPlugin extends AutoPlugin {

object autoImport {

lazy val webappResources =
lazy val webappResources: TaskKey[Map[String, File]] =
taskKey[Map[String, File]]("webapp resources")

lazy val webappClasses =
lazy val webappClasses: TaskKey[Map[String, File]] =
taskKey[Map[String, File]]("webapp classes")

lazy val webappLib =
lazy val webappLib: TaskKey[Map[String, File]] =
taskKey[Map[String, File]]("webapp lib")
}

import autoImport._

override def requires = plugins.JvmPlugin

override def projectSettings: Seq[Setting[_]] = {
lazy val webappContents: Initialize[Task[Map[String, File]]] =
Def.task {
webappResources.value ++
webappClasses.value ++
webappLib.value
}

val webappResourcesDir: Initialize[File] =
Def.setting((Compile / sourceDirectory).value / "webapp")
override val projectSettings: Seq[Setting[_]] = {

val webappResourcesTask: Initialize[Task[Map[String, File]]] =
Def.task(WebappComponents.getResources(webappResourcesDir.value))

val classpathFiles: Initialize[Task[Seq[File]]] =
Def.task((Runtime / fullClasspath).value.files)
(Compile / sourceDirectory)
.map(_ / "webapp")
.map(WebappComponents.getResources)

val webappClassesTask: Initialize[Task[Map[String, File]]] =
Def.task(WebappComponents.getClasses(classpathFiles.value))
(Runtime / fullClasspath)
.map(_.files)
.map(WebappComponents.getClasses)

val webappLibTask: Initialize[Task[Map[String, File]]] =
Def.task(WebappComponents.getLib(classpathFiles.value))
(Runtime / fullClasspath)
.map(_.files)
.map(WebappComponents.getLib)

Seq(
webappResources := webappResourcesTask.value,
webappClasses := webappClassesTask.value,
webappLib := webappLibTask.value
)
}

lazy val webappContents: Initialize[Task[Map[String, File]]] =
Def.task {
webappResources.value ++
webappClasses.value ++
webappLib.value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,56 +21,59 @@ object WebappComponentsRunnerPlugin extends AutoPlugin {

import autoImport._

override val requires: Plugins = WebappComponentsPlugin
override val requires: Plugins =
WebappComponentsPlugin

private lazy val containerInstance =
new AtomicReference[Option[WebappComponentsRunner]](None)

private val startWebapp: Initialize[Task[Unit]] =
Def.task {
stopContainerInstance()

val emptyDir: File =
WebappComponentsRunner.mkdir(
(Compile / target).value / "empty"
)

val runner: WebappComponentsRunner =
WebappComponentsRunner(
hostname = "localhost", // TODO this could be a settingKey
port = webappPort.value,
contextPath = "", // TODO this could be a settingKey
emptyWebappDir = emptyDir,
emptyClassesDir = emptyDir,
resourceMap = WebappComponentsPlugin.webappContents.value
)
runner.start()

containerInstance.set(Some(runner))
}

private val joinWebapp: Initialize[Task[Unit]] =
Def.task(containerInstance.get.foreach(_.join()))

private def stopContainerInstance(): Unit =
containerInstance.getAndSet(None).foreach(_.stop())

private val stopWebapp: Initialize[Task[Unit]] =
Def.task(stopContainerInstance())

private val onLoadSetting: Initialize[State => State] =
Def.setting {
(Global / onLoad).value compose { state: State =>
state.addExitHook(stopContainerInstance())
override val projectSettings: Seq[Setting[_]] = {

def stopContainerInstance(): Unit =
containerInstance.getAndSet(None).foreach(_.stop())

val startWebapp: Initialize[Task[Unit]] =
Def.task {
stopContainerInstance()

val emptyDir: File =
WebappComponentsRunner.mkdir(
(Compile / target).value / "empty"
)

val runner: WebappComponentsRunner =
WebappComponentsRunner(
hostname = "localhost", // TODO this could be a settingKey
port = webappPort.value,
contextPath = "", // TODO this could be a settingKey
emptyWebappDir = emptyDir,
emptyClassesDir = emptyDir,
resourceMap = WebappComponentsPlugin.webappContents.value
)
runner.start()

containerInstance.set(Some(runner))
}

val joinWebapp: Initialize[Task[Unit]] =
Def.task(containerInstance.get.foreach(_.join()))

val stopWebapp: Initialize[Task[Unit]] =
Def.task(stopContainerInstance())

val onLoadSetting: Initialize[State => State] =
Def.setting {
(Global / onLoad).value compose { state: State =>
state.addExitHook(stopContainerInstance())
}
}
}

override lazy val projectSettings: Seq[Setting[_]] =
Seq(
webappPort := 8080,
webappStart := startWebapp.value,
webappJoin := joinWebapp.value,
webappStop := stopWebapp.value,
Global / onLoad := onLoadSetting.value
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.earldouglas.sbt.war
import sbt.Def.settingKey
import sbt._

/** Launches a webapp composed of in-place resources, classes, and
* libraries.
*/
object WebappRunnerPlugin extends AutoPlugin {

object autoImport {
Expand Down
7 changes: 6 additions & 1 deletion src/sbt-test/plugins/war-package/sbt/test.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ lazy val checkWar: Def.Initialize[Task[Unit]] =
"WEB-INF/classes/drivers/mem/mem$package$.class",
"WEB-INF/classes/drivers/mem/mem$package.class",
"WEB-INF/classes/drivers/mem/mem$package.tasty",
"WEB-INF/classes/logback.xml",
"WEB-INF/classes/runners/",
"WEB-INF/classes/runners/CountServlet.class",
"WEB-INF/classes/runners/CountServlet.tasty",
Expand All @@ -86,13 +87,17 @@ lazy val checkWar: Def.Initialize[Task[Unit]] =
"WEB-INF/lib/cats-effect_3-3.5.4.jar",
"WEB-INF/lib/cats-kernel_3-2.9.0.jar",
"WEB-INF/lib/h2-2.2.224.jar",
"WEB-INF/lib/logback-classic-1.5.8.jar",
"WEB-INF/lib/logback-core-1.5.8.jar",
"WEB-INF/lib/scala-library-2.13.14.jar",
"WEB-INF/lib/scala-logging_3-3.9.5.jar",
"WEB-INF/lib/scala3-library_3-3.5.0.jar",
"WEB-INF/lib/slf4j-api-2.0.15.jar",
"WEB-INF/web.xml",
"favicon.ico",
"index.html",
"styles/",
"styles/theme.css"
"styles/theme.css",
)

val warFile: File = pkg.value
Expand Down
Loading

0 comments on commit e9256ad

Please sign in to comment.