From d2a1059b428d22033d148a06ac66ff1d4605ead6 Mon Sep 17 00:00:00 2001 From: James Earl Douglas Date: Tue, 15 Oct 2024 08:33:37 -0700 Subject: [PATCH 1/5] Merge webapp-components-runner into war-package-runner This rethinks `webappStart` as `warQuickstart`, and removes the resulting duplication. --- README.md | 149 +++++---------- build.sbt | 17 +- .../com/earldouglas/sbt/war/SbtWar.scala | 157 +++++++++++++++- .../sbt/war/WarPackagePlugin.scala | 4 +- .../sbt/war/WarPackageRunnerPlugin.scala | 110 ----------- .../sbt/war/WebappComponentsPlugin.scala | 36 ++-- .../war/WebappComponentsRunnerPlugin.scala | 145 --------------- .../sbt/war/WebappRunnerPlugin.scala | 21 --- src/sbt-test/plugins/sbt-war/sbt/test.sbt | 97 +++++----- src/sbt-test/plugins/sbt-war/test | 8 +- .../plugins/war-package-runner/sbt/test.sbt | 176 ------------------ .../plugins/war-package-runner/template.sbt | 10 - src/sbt-test/plugins/war-package-runner/test | 8 - .../webapp-components-runner/sbt/test.sbt | 176 ------------------ .../webapp-components-runner/template.sbt | 10 - .../plugins/webapp-components-runner/test | 8 - .../plugins/webapp-components/sbt/test.sbt | 6 +- 17 files changed, 278 insertions(+), 860 deletions(-) delete mode 100644 src/main/scala/com/earldouglas/sbt/war/WarPackageRunnerPlugin.scala delete mode 100644 src/main/scala/com/earldouglas/sbt/war/WebappComponentsRunnerPlugin.scala delete mode 100644 src/main/scala/com/earldouglas/sbt/war/WebappRunnerPlugin.scala delete mode 100644 src/sbt-test/plugins/war-package-runner/sbt/test.sbt delete mode 100644 src/sbt-test/plugins/war-package-runner/template.sbt delete mode 100644 src/sbt-test/plugins/war-package-runner/test delete mode 100644 src/sbt-test/plugins/webapp-components-runner/sbt/test.sbt delete mode 100644 src/sbt-test/plugins/webapp-components-runner/template.sbt delete mode 100644 src/sbt-test/plugins/webapp-components-runner/test diff --git a/README.md b/README.md index 6be08cf6..2b70e925 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Template applied in ./hello-sbt-war $ cd hello-sbt-war/ $ sbt -> webappStart +> warStart ``` ``` @@ -45,7 +45,7 @@ $ curl localhost:8080/hello ``` ``` -> webappStop +> warStop ``` ## Getting started from scratch @@ -109,11 +109,11 @@ class MyServlet extends HttpServlet: res.getWriter.write("""

Hello, world!

""") ``` -Run it from sbt with `webappStart`: +Run it from sbt with `warStart`: ``` $ sbt -> webappStart +> warStart ``` ``` @@ -121,10 +121,10 @@ $ curl localhost:8080/hello

Hello, world!

``` -Stop it with `webappStop`: +Stop it with `warStop`: ``` -> webappStop +> warStop ``` Create a .war file with `package`: @@ -135,50 +135,30 @@ Create a .war file with `package`: ## Settings -| Key | Type | Default | Notes | -| ------------------------------- | ------------------ | ----------------- | --------------------------------------------------------------------------------------- | -| `webappResources` | `Map[String,File]` | *src/main/webapp* | Static files (HTML, CSS, JS, images, etc.) to serve directly | -| `webappClasses` | `Map[String,File]` | project classes | .class files to copy into the *WEB-INF/classes* directory | -| `webappLib` | `Map[String,File]` | project libs | .jar files to copy into the *WEB-INF/lib* directory | -| `webappRunnerVersion` | `String` | `"10.1.28.0"` | The version of `com.heroku:webapp-runner` to use for running the webapp | -| `webappComponentsRunnerVersion` | `String` | `"10.1.28.0-M1"` | The version of `com.earldouglas:webapp-components-runner` to use for running the webapp | -| `webappPort` | `Int` | `8080` | The local container port to use when running with `webappStart` | -| `warPort` | `Int` | `8080` | The local container port to use when running with `warStart` | -| `webappForkOptions` | `ForkOptions` | Buffered output | Options for the forked JVM used when running with `webappStart` | -| `warForkOptions` | `ForkOptions` | Buffered output | Options for the forked JVM used when running with `warStart` | +| Key | Type | Default | Notes | +| ------------------ | ------------------ | ----------------- | --------------------------------------------------------------------------- | +| `warResources` | `Map[String,File]` | *src/main/webapp* | Static files (HTML, CSS, JS, images, etc.) to serve directly | +| `warClasses` | `Map[String,File]` | project classes | .class files to copy into the *WEB-INF/classes* directory | +| `warLib` | `Map[String,File]` | project libs | .jar files to copy into the *WEB-INF/lib* directory | +| `warRunnerVersion` | `String` | `"10.1.28.0-M1"` | The version of `com.earldouglas:webapp-components-runner` to run the webapp | +| `warPort` | `Int` | `8080` | The local container port to use when running with `warStart` | +| `warForkOptions` | [`ForkOptions`] | Buffered output | Options for the forked JVM used when running with `warStart` | ## Commands -| Key | Notes | -| ------------- | ----------------------------------------------------------------------- | -| `webappStart` | Starts a local container, serving content directly from project sources | -| `webappJoin` | Blocks until the container shuts down | -| `webappStop` | Shuts down the container | -| `warStart` | Starts a local container, serving content from the packaged .war file | -| `warJoin` | Blocks until the container shuts down | -| `warStop` | Shuts down the container | - -### `war` vs. `webapp` - -Settings and commands that begin with `war` apply to the packaged .war -file, which includes resources, classes, and libraries. The development -cycle can be sped up by serving resources, classes, and libraries -directly from source, avoiding the overhead of packaging a -*.war* file. +| Key | Notes | +| --------------- | ----------------------------------------------------------------------- | +| `warStart` | Starts a local container, serving content from the packaged .war file | +| `warQuickstart` | Starts a local container, serving content directly from project sources | +| `warJoin` | Blocks until the container shuts down | +| `warStop` | Shuts down the container | -Use the `webapp` prefix in place of `war` to skip packaging, and run the -container directly from source: +### `warResources` -``` -> webappStart -``` - -### `webappResources` +Resources are the various static files, deployment descriptors, etc. +that go into a .war file. -Webapp resources are the various static files, deployment descriptors, -etc. that go into a .war file. - -The `webappResources` setting is a mapping from destination to source of +The `warResources` setting is a mapping from destination to source of these files. The destination is a path relative to the contents of the .war file. The source is a path on the local filesystem. @@ -197,7 +177,7 @@ myproject.war └── MANIFEST.MF ``` -The `webappResources` mapping would look like this: +The `warResources` mapping would look like this: ``` "index.html" -> File(".../src/main/webapp/index.html") @@ -208,7 +188,7 @@ The `webappResources` mapping would look like this: To use a different directory, e.g. *src/main/WebContent*: ```scala -webappResources := +warResources := (Compile / sourceDirectory) .map(_ / "WebContent") .map(WebappComponents.getResources) @@ -224,7 +204,7 @@ sbt.Keys.`package` / packageOptions += ) ``` -### `webappClasses` +### `warClasses` By default, project classes are copied into the *WEB-INF/classes* directory of the *.war* file. To package them in a *.jar* file in the @@ -238,7 +218,7 @@ See ["Configure packaging"](https://www.scala-sbt.org/1.x/docs/Howto-Package.html) in the sbt documentation for additional information. -### `webappLib` +### `warLib` By default, all runtime dependencies are copied into the *WEB-INF/lib* directory. @@ -250,40 +230,26 @@ set its scope to `Provided`: libraryDependencies += "foo" % "bar" % "1.0.0" % Provided ``` -### `webappRunnerVersion` - -By default, [Webapp Runner](https://github.com/heroku/webapp-runner) -10.1.x is used to run the .war file in a forked JVM. To use a different -version, set `webappRunnerVersion`: - -```scala -webappRunnerVersion := "9.0.93.0" -``` - -### `webappComponentsRunnerVersion` +### `warRunnerVersion` By default, [Webapp Components Runner](https://github.com/earldouglas/webapp-components-runner) 10.1.x is used to run the webapp in a forked JVM. To use a different version, -set `webappComponentsRunnerVersion`: +set `warRunnerVersion`: ```scala -webappComponentsRunnerVersion := "9.0.93.0.0" +warRunnerVersion := "9.0.93.0.0" ``` -### `warPort` and `webappPort` +### `warPort` By default, the container runs on port *8080*. To use a different port, -set `warPort`/`webappPort`: +set `warPort`: ```scala warPort := 9090 ``` -```scala -webappPort := 9090 -``` - ### `warForkOptions` To set environment variables, system properties, and more for the @@ -306,46 +272,23 @@ warForkOptions := ) ``` -### `webappForkOptions` - -To set environment variables, system properties, and more for the -forked container JVM, set a -[ForkOptions](https://www.scala-sbt.org/1.x/api/sbt/ForkOptions.html) -instance via `webappForkOptions`. - -For example: to attach a debugger, set `-Xdebug` and `-Xrunjdwp`: - -*build.sbt:* - -```scala -webappForkOptions := - ForkOptions() - .withRunJVMOptions( - Seq( - "-Xdebug", - "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000" - ) - ) -``` - -### `webappStart` and `warStart` - -``` -> webappStart -``` +### `warStart` and `warQuickstart` ``` > warStart ``` -### `webappJoin` and `warJoin` - -These can be used to block sbt while the container is running: +To skip packaging the .war file before launching the container, use +`warQuickstart`: ``` -$ sbt webappStart webappJoin +> warQuickstart ``` +### `warJoin` + +This can be used to block sbt while the container is running: + ``` $ sbt warStart warJoin ``` @@ -353,14 +296,12 @@ $ sbt warStart warJoin This is useful for running sbt in production (e.g. in a Docker container), if you're into that kind of thing. -### `webappStop` and `warStop` +### `warStop` -These can be used to stop the running container: - -``` -> webappStop -``` +This can be used to stop the running container: ``` > warStop ``` + +[`ForkOptions`]: https://www.scala-sbt.org/1.x/api/sbt/ForkOptions.html diff --git a/build.sbt b/build.sbt index cb42974c..3d3a57d8 100644 --- a/build.sbt +++ b/build.sbt @@ -20,24 +20,15 @@ semanticdbVersion := scalafixSemanticdb.revision scalacOptions += "-Ywarn-unused-import" scalacOptions += s"-P:semanticdb:sourceroot:${baseDirectory.value}" -// webapp-runner -lazy val webappRunnerVersion = - settingKey[String]("webapp-runner version") -webappRunnerVersion := "10.1.28.0" -libraryDependencies += "com.heroku" % "webapp-runner" % webappRunnerVersion.value % Provided - // webapp-components-runner -lazy val webappComponentsRunnerVersion = +lazy val warRunnerVersion = settingKey[String]("webapp-components-runner version") -webappComponentsRunnerVersion := "10.1.28.0.0-M1" -libraryDependencies += "com.earldouglas" % "webapp-components-runner" % webappComponentsRunnerVersion.value % Provided +warRunnerVersion := "10.1.28.0.0-M1" +libraryDependencies += "com.earldouglas" % "webapp-components-runner" % warRunnerVersion.value % Provided // sbt-buildinfo enablePlugins(BuildInfoPlugin) -buildInfoKeys := Seq[BuildInfoKey]( - webappRunnerVersion, - webappComponentsRunnerVersion -) +buildInfoKeys := Seq[BuildInfoKey](warRunnerVersion) buildInfoPackage := "com.earldouglas.sbt.war" // Testing diff --git a/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala b/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala index 8bd73c39..e7b9816d 100644 --- a/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala +++ b/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala @@ -1,7 +1,19 @@ package com.earldouglas.sbt.war import sbt.AutoPlugin +import sbt.Def.Initialize +import sbt.Def.settingKey +import sbt.Keys._ +import sbt.Keys._ +import sbt.Keys.{`package` => pkg} import sbt.Plugins +import sbt._ +import sbt._ + +import java.nio.file.Files +import java.nio.file.Paths +import java.util.concurrent.atomic.AtomicReference +import scala.sys.process.{Process => ScalaProcess} /** The top-level plugin to be used by default. From the required * plugins, this brings in all of the webapp components mappings, .war @@ -10,6 +22,149 @@ import sbt.Plugins */ object SbtWar extends AutoPlugin { + object autoImport { + lazy val War = config("war").hide + lazy val warPort = settingKey[Int]("container port") + lazy val warStart = taskKey[Unit]("start container") + lazy val warJoin = taskKey[Unit]("join container") + lazy val warStop = taskKey[Unit]("stop container") + lazy val warQuickstart = taskKey[Unit]("quickstart container") + lazy val warForkOptions = + taskKey[ForkOptions]("container fork options") + } + + import autoImport._ + override val requires: Plugins = - WarPackageRunnerPlugin && WebappComponentsRunnerPlugin + WarPackagePlugin + + override val projectConfigurations: Seq[Configuration] = + Seq(War) + + private lazy val containerInstance = + new AtomicReference[Option[ScalaProcess]](None) + + override val projectSettings: Seq[Setting[_]] = { + + def stopContainerInstance(log: String => Unit): Unit = + containerInstance + .getAndSet(None) + .foreach { p => + log("[sbt-war] Starting server") + p.destroy() + } + + val runnerJars: Initialize[Task[Seq[File]]] = + Def.task { + Classpaths + .managedJars(War, classpathTypes.value, update.value) + .map(_.data) + .toList + } + + val startWar: Initialize[Task[Unit]] = + Def.task { + stopContainerInstance(streams.value.log.info(_)) + + streams.value.log.info("[sbt-war] Starting server") + val process: ScalaProcess = + Fork.java.fork( + warForkOptions.value, + Seq( + "-cp", + Path.makeString(runnerJars.value), + "webapp.runner.launch.Main", + "--port", + warPort.value.toString(), + pkg.value.getPath() + ) + ) + containerInstance.set(Some(process)) + } + + val joinWar: Initialize[Task[Unit]] = + Def.task(containerInstance.get.map(_.exitValue())) + + val stopWar: Initialize[Task[Unit]] = + Def.task(stopContainerInstance(streams.value.log.info(_))) + + val onLoadSetting: Initialize[State => State] = + Def.setting { + (Global / onLoad).value + .compose { state: State => + state.addExitHook(stopContainerInstance(println(_))) + } + } + + val forkOptions: Initialize[Task[ForkOptions]] = + Def.task { + ForkOptions() + .withOutputStrategy(Some(BufferedOutput(streams.value.log))) + } + + val runnerLibraries: Initialize[Seq[ModuleID]] = + Def.setting { + Seq( + "com.earldouglas" % "webapp-components-runner" % BuildInfo.warRunnerVersion % War + ) + } + + val quickstartWar: Initialize[Task[Unit]] = + Def.task { + + val runnerConfigFile: File = { + + val emptyDir: File = (Compile / target).value / "empty" + + val resourceMapString = + WebappComponentsPlugin.warContents.value + .map { case (k, v) => + s"${k}->${v}" + } + .mkString(",") + + val configurationFile: File = + (Compile / target).value / "webapp-components.properties" + + Files + .writeString( + Paths.get(configurationFile.getPath()), + s"""|hostname=localhost + |port=${warPort.value} + |contextPath= + |emptyWebappDir=${emptyDir} + |emptyClassesDir=${emptyDir} + |resourceMap=${resourceMapString} + |""".stripMargin + ) + .toFile() + } + + stopContainerInstance(streams.value.log.info(_)) + + streams.value.log.info("[sbt-war] Quickstarting server") + val process: ScalaProcess = + Fork.java.fork( + warForkOptions.value, + Seq( + "-cp", + Path.makeString(runnerJars.value), + "com.earldouglas.WebappComponentsRunner", + runnerConfigFile.getPath() + ) + ) + containerInstance.set(Some(process)) + } + + Seq( + Global / onLoad := onLoadSetting.value, + libraryDependencies ++= runnerLibraries.value, + warForkOptions := forkOptions.value, + warJoin := joinWar.value, + warPort := 8080, + warQuickstart := quickstartWar.value, + warStart := startWar.value, + warStop := stopWar.value + ) + } } diff --git a/src/main/scala/com/earldouglas/sbt/war/WarPackagePlugin.scala b/src/main/scala/com/earldouglas/sbt/war/WarPackagePlugin.scala index b8d0e4f9..a2a493bc 100644 --- a/src/main/scala/com/earldouglas/sbt/war/WarPackagePlugin.scala +++ b/src/main/scala/com/earldouglas/sbt/war/WarPackagePlugin.scala @@ -20,9 +20,9 @@ object WarPackagePlugin extends AutoPlugin { override lazy val projectSettings: Seq[Setting[_]] = { - // Flip webappContents around from (dst -> src) to (src -> dst) + // Flip warContents around from (dst -> src) to (src -> dst) val packageContents: Initialize[Task[Seq[(java.io.File, String)]]] = - WebappComponentsPlugin.webappContents + WebappComponentsPlugin.warContents .map(_.map(_.swap).toSeq) val packageTaskSettings: Seq[Setting[_]] = diff --git a/src/main/scala/com/earldouglas/sbt/war/WarPackageRunnerPlugin.scala b/src/main/scala/com/earldouglas/sbt/war/WarPackageRunnerPlugin.scala deleted file mode 100644 index 47460841..00000000 --- a/src/main/scala/com/earldouglas/sbt/war/WarPackageRunnerPlugin.scala +++ /dev/null @@ -1,110 +0,0 @@ -package com.earldouglas.sbt.war - -import sbt.Def.Initialize -import sbt.Def.settingKey -import sbt.Keys._ -import sbt.Keys.{`package` => pkg} -import sbt._ - -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 com.heroku:webapp-runner. - */ -object WarPackageRunnerPlugin extends AutoPlugin { - - object autoImport { - lazy val War = config("war").hide - lazy val warPort = settingKey[Int]("war container port") - 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 = - taskKey[ForkOptions]("war container fork options") - lazy val warMainClass = - settingKey[String]("war container main class") - } - - import autoImport._ - import WebappRunnerPlugin.autoImport._ - - override val requires: Plugins = - WarPackagePlugin && WebappRunnerPlugin - - override val projectConfigurations: Seq[Configuration] = - Seq(War) - - private lazy val containerInstance = - new AtomicReference[Option[ScalaProcess]](None) - - 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 runnerJars: Seq[File] = - Classpaths - .managedJars(War, classpathTypes.value, update.value) - .map(_.data) - .toList - - streams.value.log.info("[sbt-war] Starting server") - val process: ScalaProcess = - Fork.java.fork( - warForkOptions.value, - Seq( - "-cp", - Path.makeString(runnerJars), - warMainClass.value, - "--port", - warPort.value.toString(), - pkg.value.getPath() - ) - ) - containerInstance.set(Some(process)) - } - - val joinWar: Initialize[Task[Unit]] = - Def.task(containerInstance.get.map(_.exitValue())) - - val stopWar: Initialize[Task[Unit]] = - Def.task(stopContainerInstance()) - - val onLoadSetting: Initialize[State => State] = - Def.setting { - (Global / onLoad).value - .compose { state: State => - state.addExitHook(stopContainerInstance()) - } - } - - val forkOptions: Initialize[Task[ForkOptions]] = - Def.task { - ForkOptions() - .withOutputStrategy(Some(BufferedOutput(streams.value.log))) - } - - val runnerLibrary: Initialize[ModuleID] = - Def.setting { - ("com.heroku" % "webapp-runner" % webappRunnerVersion.value intransitive ()) % War - } - - Seq( - warPort := 8080, - warStart := startWar.value, - warJoin := joinWar.value, - warStop := stopWar.value, - warForkOptions := forkOptions.value, - Global / onLoad := onLoadSetting.value, - libraryDependencies += runnerLibrary.value, - warMainClass := "webapp.runner.launch.Main" - ) - } -} diff --git a/src/main/scala/com/earldouglas/sbt/war/WebappComponentsPlugin.scala b/src/main/scala/com/earldouglas/sbt/war/WebappComponentsPlugin.scala index 17e07119..fd8bd327 100644 --- a/src/main/scala/com/earldouglas/sbt/war/WebappComponentsPlugin.scala +++ b/src/main/scala/com/earldouglas/sbt/war/WebappComponentsPlugin.scala @@ -11,13 +11,13 @@ import sbt._ * * 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 + * - warResources: 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 + * - warClasses: 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. + * - warLib: 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. @@ -28,13 +28,13 @@ object WebappComponentsPlugin extends AutoPlugin { object autoImport { - lazy val webappResources: TaskKey[Map[String, File]] = + lazy val warResources: TaskKey[Map[String, File]] = taskKey[Map[String, File]]("webapp resources") - lazy val webappClasses: TaskKey[Map[String, File]] = + lazy val warClasses: TaskKey[Map[String, File]] = taskKey[Map[String, File]]("webapp classes") - lazy val webappLib: TaskKey[Map[String, File]] = + lazy val warLib: TaskKey[Map[String, File]] = taskKey[Map[String, File]]("webapp lib") } @@ -42,34 +42,34 @@ object WebappComponentsPlugin extends AutoPlugin { override def requires = plugins.JvmPlugin - lazy val webappContents: Initialize[Task[Map[String, File]]] = + lazy val warContents: Initialize[Task[Map[String, File]]] = Def.task { - webappResources.value ++ - webappClasses.value ++ - webappLib.value + warResources.value ++ + warClasses.value ++ + warLib.value } override val projectSettings: Seq[Setting[_]] = { - val webappResourcesTask: Initialize[Task[Map[String, File]]] = + val warResourcesTask: Initialize[Task[Map[String, File]]] = (Compile / sourceDirectory) .map(_ / "webapp") .map(WebappComponents.getResources) - val webappClassesTask: Initialize[Task[Map[String, File]]] = + val warClassesTask: Initialize[Task[Map[String, File]]] = (Runtime / fullClasspath) .map(_.files) .map(WebappComponents.getClasses) - val webappLibTask: Initialize[Task[Map[String, File]]] = + val warLibTask: Initialize[Task[Map[String, File]]] = (Runtime / fullClasspath) .map(_.files) .map(WebappComponents.getLib) Seq( - webappResources := webappResourcesTask.value, - webappClasses := webappClassesTask.value, - webappLib := webappLibTask.value + warResources := warResourcesTask.value, + warClasses := warClassesTask.value, + warLib := warLibTask.value ) } } diff --git a/src/main/scala/com/earldouglas/sbt/war/WebappComponentsRunnerPlugin.scala b/src/main/scala/com/earldouglas/sbt/war/WebappComponentsRunnerPlugin.scala deleted file mode 100644 index 96828efb..00000000 --- a/src/main/scala/com/earldouglas/sbt/war/WebappComponentsRunnerPlugin.scala +++ /dev/null @@ -1,145 +0,0 @@ -package com.earldouglas.sbt.war - -import sbt.Def.Initialize -import sbt.Def.settingKey -import sbt.Keys._ -import sbt._ - -import java.nio.file.Files -import java.nio.file.Paths -import java.util.concurrent.atomic.AtomicReference -import scala.sys.process.{Process => ScalaProcess} - -/** Launches the webapp components managed by WebappComponentsPlugin. - * Uses a forked JVM to run Tomcat via - * com.earldouglas:webapp-components-runner. - */ -object WebappComponentsRunnerPlugin extends AutoPlugin { - - object autoImport { - lazy val Webapp = config("webapp").hide - lazy val webappPort = settingKey[Int]("webapp container port") - lazy val webappStart = taskKey[Unit]("start webapp container") - lazy val webappJoin = taskKey[Unit]("join webapp container") - lazy val webappStop = taskKey[Unit]("stop webapp container") - lazy val webappForkOptions = - taskKey[ForkOptions]("webapp container fork options") - lazy val webappComponentsRunnerVersion = - settingKey[String]("webapp-components-runner version") - lazy val webappComponentsRunnerMainClass = - settingKey[String]("webapp-components-runner main class") - } - - import autoImport._ - import WebappRunnerPlugin.autoImport._ - - override val requires: Plugins = - WebappComponentsPlugin && WebappRunnerPlugin - - override val projectConfigurations: Seq[Configuration] = - Seq(Webapp) - - private lazy val containerInstance = - new AtomicReference[Option[ScalaProcess]](None) - - override val projectSettings: Seq[Setting[_]] = { - - def stopContainerInstance(): Unit = { - val oldProcess = containerInstance.getAndSet(None) - oldProcess.foreach(_.destroy()) - } - - val runnerConfigFile: Initialize[Task[File]] = - Def.task { - - val emptyDir: File = (Compile / target).value / "empty" - - val resourceMapString = - WebappComponentsPlugin.webappContents.value - .map { case (k, v) => - s"${k}->${v}" - } - .mkString(",") - - val configurationFile: File = - (Compile / target).value / "webapp-components.properties" - - Files - .writeString( - Paths.get(configurationFile.getPath()), - s"""|hostname=localhost - |port=${webappPort.value} - |contextPath= - |emptyWebappDir=${emptyDir} - |emptyClassesDir=${emptyDir} - |resourceMap=${resourceMapString} - |""".stripMargin - ) - .toFile() - } - - val startWebapp: Initialize[Task[Unit]] = - Def.task { - stopContainerInstance() - - val runnerJars: Seq[File] = - Classpaths - .managedJars(Webapp, classpathTypes.value, update.value) - .map(_.data) - .toList - - streams.value.log.info("[sbt-war] Starting server") - val process: ScalaProcess = - Fork.java.fork( - webappForkOptions.value, - Seq( - "-cp", - Path.makeString(runnerJars), - webappComponentsRunnerMainClass.value, - runnerConfigFile.value.getPath() - ) - ) - containerInstance.set(Some(process)) - } - - val joinWebapp: Initialize[Task[Unit]] = - Def.task(containerInstance.get.map(_.exitValue())) - - val stopWebapp: Initialize[Task[Unit]] = - Def.task(stopContainerInstance()) - - val forkOptions: Initialize[Task[ForkOptions]] = - Def.task { - ForkOptions() - .withOutputStrategy(Some(BufferedOutput(streams.value.log))) - } - - val onLoadSetting: Initialize[State => State] = - Def.setting { - (Global / onLoad).value - .compose { state: State => - state.addExitHook(stopContainerInstance()) - } - } - - val runnerLibraries: Initialize[Seq[ModuleID]] = - Def.setting { - Seq( - "com.earldouglas" % "webapp-components-runner" % webappComponentsRunnerVersion.value % Webapp, - "com.heroku" % "webapp-runner" % webappRunnerVersion.value % Webapp - ) - } - - Seq( - webappPort := 8080, - webappStart := startWebapp.value, - webappJoin := joinWebapp.value, - webappStop := stopWebapp.value, - webappForkOptions := forkOptions.value, - Global / onLoad := onLoadSetting.value, - webappComponentsRunnerVersion := BuildInfo.webappComponentsRunnerVersion, - libraryDependencies ++= runnerLibraries.value, - webappComponentsRunnerMainClass := "com.earldouglas.WebappComponentsRunner" - ) - } -} diff --git a/src/main/scala/com/earldouglas/sbt/war/WebappRunnerPlugin.scala b/src/main/scala/com/earldouglas/sbt/war/WebappRunnerPlugin.scala deleted file mode 100644 index 4568d89d..00000000 --- a/src/main/scala/com/earldouglas/sbt/war/WebappRunnerPlugin.scala +++ /dev/null @@ -1,21 +0,0 @@ -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 { - lazy val webappRunnerVersion = - settingKey[String]("webapp-runner version") - } - - import autoImport._ - - override lazy val projectSettings = - Seq( - webappRunnerVersion := BuildInfo.webappRunnerVersion - ) -} diff --git a/src/sbt-test/plugins/sbt-war/sbt/test.sbt b/src/sbt-test/plugins/sbt-war/sbt/test.sbt index f5207ce5..13a0546e 100644 --- a/src/sbt-test/plugins/sbt-war/sbt/test.sbt +++ b/src/sbt-test/plugins/sbt-war/sbt/test.sbt @@ -1,7 +1,6 @@ enablePlugins(SbtWar) -webappPort := 8083 -warPort := 8085 +warPort := 8081 TaskKey[Unit]("await-open") := { @@ -27,7 +26,6 @@ TaskKey[Unit]("await-open") := { } } - awaitOpen(webappPort.value) awaitOpen(warPort.value) } @@ -55,7 +53,6 @@ TaskKey[Unit]("await-closed") := { } } - awaitClosed(webappPort.value) awaitClosed(warPort.value) } @@ -92,7 +89,7 @@ TaskKey[Unit]("check") := { .openConnection .asInstanceOf[HttpURLConnection] - val name: String = s"WarRunnerPlugin: GET ${url}" + val name: String = s"SbtWar: GET ${url}" c.setInstanceFollowRedirects(false) c.setRequestMethod("GET") @@ -133,53 +130,47 @@ TaskKey[Unit]("check") := { } } - def check(port: Int): Unit = { - - assertEquals( - url = s"http://localhost:${port}/", - expectedBody = Source - .fromFile((Compile / sourceDirectory).value / "webapp" / "index.html") - .mkString - ) - - assertEquals( - url = s"http://localhost:${port}/count", - expectedBody = """|{ - | "count": 1 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${port}/count", - expectedBody = """|{ - | "count": 2 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${port}/count", - expectedBody = """|{ - | "count": 3 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${port}/count", - expectedBody = """|{ - | "count": 4 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${port}/hello", - expectedBody = """

Hello, world!

""" - ) - } + assertEquals( + url = s"http://localhost:${warPort.value}/", + expectedBody = Source + .fromFile((Compile / sourceDirectory).value / "webapp" / "index.html") + .mkString + ) + + assertEquals( + url = s"http://localhost:${warPort.value}/count", + expectedBody = """|{ + | "count": 1 + |} + |""".stripMargin + ) + + assertEquals( + url = s"http://localhost:${warPort.value}/count", + expectedBody = """|{ + | "count": 2 + |} + |""".stripMargin + ) - check(webappPort.value) - check(warPort.value) + assertEquals( + url = s"http://localhost:${warPort.value}/count", + expectedBody = """|{ + | "count": 3 + |} + |""".stripMargin + ) + + assertEquals( + url = s"http://localhost:${warPort.value}/count", + expectedBody = """|{ + | "count": 4 + |} + |""".stripMargin + ) + + assertEquals( + url = s"http://localhost:${warPort.value}/hello", + expectedBody = """

Hello, world!

""" + ) } diff --git a/src/sbt-test/plugins/sbt-war/test b/src/sbt-test/plugins/sbt-war/test index bb9a24a1..2f6d91a6 100644 --- a/src/sbt-test/plugins/sbt-war/test +++ b/src/sbt-test/plugins/sbt-war/test @@ -1,10 +1,14 @@ > setup > reload -> webappStart +> warQuickstart +> awaitOpen +> check +> warStop +> awaitClosed + > warStart > awaitOpen > check -> webappStop > warStop > awaitClosed diff --git a/src/sbt-test/plugins/war-package-runner/sbt/test.sbt b/src/sbt-test/plugins/war-package-runner/sbt/test.sbt deleted file mode 100644 index ad5e4dba..00000000 --- a/src/sbt-test/plugins/war-package-runner/sbt/test.sbt +++ /dev/null @@ -1,176 +0,0 @@ -enablePlugins(WarPackageRunnerPlugin) - -warPort := 8081 - -TaskKey[Unit]("await-open") := { - - def isOpen(port: Int): Boolean = - try { - import java.net.Socket - import java.net.InetSocketAddress - val socket: Socket = new Socket() - socket.connect(new InetSocketAddress("localhost", port)) - socket.close() - true - } catch { - case e: Exception => false - } - - def awaitOpen(port: Int, retries: Int = 40): Unit = - if (!isOpen(port)) { - if (retries > 0) { - Thread.sleep(250) - awaitOpen(port, retries - 1) - } else { - throw new Exception(s"expected port $port to be open") - } - } - - awaitOpen(warPort.value) -} - -TaskKey[Unit]("await-closed") := { - - def isOpen(port: Int): Boolean = - try { - import java.net.Socket - import java.net.InetSocketAddress - val socket: Socket = new Socket() - socket.connect(new InetSocketAddress("localhost", port)) - socket.close() - true - } catch { - case e: Exception => false - } - - def awaitClosed(port: Int, retries: Int = 40): Unit = - if (isOpen(port)) { - if (retries > 0) { - Thread.sleep(250) - awaitClosed(port, retries - 1) - } else { - throw new Exception(s"expected port $port to be closed") - } - } - - awaitClosed(warPort.value) -} - -TaskKey[Unit]("check") := { - - import java.net.HttpURLConnection - import java.net.URI - import java.net.URL - import javax.net.ssl.HostnameVerifier - import javax.net.ssl.HttpsURLConnection - import javax.net.ssl.SSLSession - import scala.io.Source - - val log: sbt.internal.util.ManagedLogger = streams.value.log - - HttpsURLConnection.setDefaultHostnameVerifier( - new HostnameVerifier() { - override def verify( - hostname: String, - sslSession: SSLSession - ): Boolean = { - hostname == "localhost" - } - } - ) - - def assertEquals( - url: String, - expectedBody: String - ): Unit = { - val c: HttpURLConnection = - (new URI(url) - .toURL()) - .openConnection - .asInstanceOf[HttpURLConnection] - - val name: String = s"WarRunnerPlugin: GET ${url}" - - c.setInstanceFollowRedirects(false) - c.setRequestMethod("GET") - c.setDoOutput(false) - - val obtainedStatus: Int = - c.getResponseCode() - val obtainedBody: String = - Source.fromInputStream(c.getInputStream()).mkString - - val expectedStatus: Int = 200 - - val statusMatch: Boolean = - expectedStatus == obtainedStatus - val bodyMatch: Boolean = - expectedBody == obtainedBody - - if (!statusMatch || !bodyMatch) { - log.error(name) - sys.error( - s"""|${name}: - | expected: - | * status: ${expectedStatus} - | * body: - | > ${expectedBody - .toString() - .replaceAll("\n", "\n > ")} - | obtained: - | * status: ${obtainedStatus} - | * body: - | > ${obtainedBody - .toString() - .replaceAll("\n", "\n > ")} - |""".stripMargin - ) - } else { - log.success(name) - } - } - - assertEquals( - url = s"http://localhost:${warPort.value}/", - expectedBody = Source - .fromFile((Compile / sourceDirectory).value / "webapp" / "index.html") - .mkString - ) - - assertEquals( - url = s"http://localhost:${warPort.value}/count", - expectedBody = """|{ - | "count": 1 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${warPort.value}/count", - expectedBody = """|{ - | "count": 2 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${warPort.value}/count", - expectedBody = """|{ - | "count": 3 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${warPort.value}/count", - expectedBody = """|{ - | "count": 4 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${warPort.value}/hello", - expectedBody = """

Hello, world!

""" - ) -} diff --git a/src/sbt-test/plugins/war-package-runner/template.sbt b/src/sbt-test/plugins/war-package-runner/template.sbt deleted file mode 100644 index 4cecf3dc..00000000 --- a/src/sbt-test/plugins/war-package-runner/template.sbt +++ /dev/null @@ -1,10 +0,0 @@ -TaskKey[Unit]("setup") := { - sbt.io.IO.copyDirectory( - new java.io.File(sys.props("templateDirectory")), - baseDirectory.value - ) - sbt.io.IO.copyFile( - new java.io.File("sbt", "test.sbt"), - new java.io.File("test.sbt") - ) -} diff --git a/src/sbt-test/plugins/war-package-runner/test b/src/sbt-test/plugins/war-package-runner/test deleted file mode 100644 index f0afc7bc..00000000 --- a/src/sbt-test/plugins/war-package-runner/test +++ /dev/null @@ -1,8 +0,0 @@ -> setup -> reload - -> warStart -> awaitOpen -> check -> warStop -> awaitClosed diff --git a/src/sbt-test/plugins/webapp-components-runner/sbt/test.sbt b/src/sbt-test/plugins/webapp-components-runner/sbt/test.sbt deleted file mode 100644 index 1fa16e53..00000000 --- a/src/sbt-test/plugins/webapp-components-runner/sbt/test.sbt +++ /dev/null @@ -1,176 +0,0 @@ -enablePlugins(WebappComponentsRunnerPlugin) - -webappPort := 8082 - -TaskKey[Unit]("await-open") := { - - def isOpen(port: Int): Boolean = - try { - import java.net.Socket - import java.net.InetSocketAddress - val socket: Socket = new Socket() - socket.connect(new InetSocketAddress("localhost", port)) - socket.close() - true - } catch { - case e: Exception => false - } - - def awaitOpen(port: Int, retries: Int = 40): Unit = - if (!isOpen(port)) { - if (retries > 0) { - Thread.sleep(250) - awaitOpen(port, retries - 1) - } else { - throw new Exception(s"expected port $port to be open") - } - } - - awaitOpen(webappPort.value) -} - -TaskKey[Unit]("await-closed") := { - - def isOpen(port: Int): Boolean = - try { - import java.net.Socket - import java.net.InetSocketAddress - val socket: Socket = new Socket() - socket.connect(new InetSocketAddress("localhost", port)) - socket.close() - true - } catch { - case e: Exception => false - } - - def awaitClosed(port: Int, retries: Int = 40): Unit = - if (isOpen(port)) { - if (retries > 0) { - Thread.sleep(250) - awaitClosed(port, retries - 1) - } else { - throw new Exception(s"expected port $port to be closed") - } - } - - awaitClosed(webappPort.value) -} - -TaskKey[Unit]("check") := { - - import java.net.HttpURLConnection - import java.net.URI - import java.net.URL - import javax.net.ssl.HostnameVerifier - import javax.net.ssl.HttpsURLConnection - import javax.net.ssl.SSLSession - import scala.io.Source - - val log: sbt.internal.util.ManagedLogger = streams.value.log - - HttpsURLConnection.setDefaultHostnameVerifier( - new HostnameVerifier() { - override def verify( - hostname: String, - sslSession: SSLSession - ): Boolean = { - hostname == "localhost" - } - } - ) - - def assertEquals( - url: String, - expectedBody: String - ): Unit = { - val c: HttpURLConnection = - (new URI(url) - .toURL()) - .openConnection - .asInstanceOf[HttpURLConnection] - - val name: String = s"WebappComponentsRunnerPlugin: GET ${url}" - - c.setInstanceFollowRedirects(false) - c.setRequestMethod("GET") - c.setDoOutput(false) - - val obtainedStatus: Int = - c.getResponseCode() - val obtainedBody: String = - Source.fromInputStream(c.getInputStream()).mkString - - val expectedStatus: Int = 200 - - val statusMatch: Boolean = - expectedStatus == obtainedStatus - val bodyMatch: Boolean = - expectedBody == obtainedBody - - if (!statusMatch || !bodyMatch) { - log.error(name) - sys.error( - s"""|${name}: - | expected: - | * status: ${expectedStatus} - | * body: - | > ${expectedBody - .toString() - .replaceAll("\n", "\n > ")} - | obtained: - | * status: ${obtainedStatus} - | * body: - | > ${obtainedBody - .toString() - .replaceAll("\n", "\n > ")} - |""".stripMargin - ) - } else { - log.success(name) - } - } - - assertEquals( - url = s"http://localhost:${webappPort.value}/", - expectedBody = Source - .fromFile((Compile / sourceDirectory).value / "webapp" / "index.html") - .mkString - ) - - assertEquals( - url = s"http://localhost:${webappPort.value}/count", - expectedBody = """|{ - | "count": 1 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${webappPort.value}/count", - expectedBody = """|{ - | "count": 2 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${webappPort.value}/count", - expectedBody = """|{ - | "count": 3 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${webappPort.value}/count", - expectedBody = """|{ - | "count": 4 - |} - |""".stripMargin - ) - - assertEquals( - url = s"http://localhost:${webappPort.value}/hello", - expectedBody = """

Hello, world!

""" - ) -} diff --git a/src/sbt-test/plugins/webapp-components-runner/template.sbt b/src/sbt-test/plugins/webapp-components-runner/template.sbt deleted file mode 100644 index 4cecf3dc..00000000 --- a/src/sbt-test/plugins/webapp-components-runner/template.sbt +++ /dev/null @@ -1,10 +0,0 @@ -TaskKey[Unit]("setup") := { - sbt.io.IO.copyDirectory( - new java.io.File(sys.props("templateDirectory")), - baseDirectory.value - ) - sbt.io.IO.copyFile( - new java.io.File("sbt", "test.sbt"), - new java.io.File("test.sbt") - ) -} diff --git a/src/sbt-test/plugins/webapp-components-runner/test b/src/sbt-test/plugins/webapp-components-runner/test deleted file mode 100644 index ed33006a..00000000 --- a/src/sbt-test/plugins/webapp-components-runner/test +++ /dev/null @@ -1,8 +0,0 @@ -> setup -> reload - -> webappStart -> awaitOpen -> check -> webappStop -> awaitClosed diff --git a/src/sbt-test/plugins/webapp-components/sbt/test.sbt b/src/sbt-test/plugins/webapp-components/sbt/test.sbt index 13f70ac1..483e8a05 100644 --- a/src/sbt-test/plugins/webapp-components/sbt/test.sbt +++ b/src/sbt-test/plugins/webapp-components/sbt/test.sbt @@ -75,7 +75,7 @@ val checkClasses: Def.Initialize[Task[Unit]] = .map(x => s"WEB-INF/classes/${x}" -> root / x) .toMap }, - obtained = webappClasses.value + obtained = warClasses.value ) } @@ -128,7 +128,7 @@ val checkLib: Def.Initialize[Task[Unit]] = assertContains( name = "WebappComponentsPlugin: checkLib", expected = expected.map(x => s"WEB-INF/lib/${x}"), - obtained = webappLib.value + obtained = warLib.value ) } @@ -178,6 +178,6 @@ lazy val checkResources: Def.Initialize[Task[Unit]] = .map(x => x -> root / "webapp" / x) .toMap }, - obtained = webappResources.value + obtained = warResources.value ) } From 952c7099e304fbff65cdf3655ca494d667cdc50d Mon Sep 17 00:00:00 2001 From: James Earl Douglas Date: Tue, 15 Oct 2024 09:11:48 -0700 Subject: [PATCH 2/5] Add link to BufferedOutput --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2b70e925..313e4a68 100644 --- a/README.md +++ b/README.md @@ -135,14 +135,14 @@ Create a .war file with `package`: ## Settings -| Key | Type | Default | Notes | -| ------------------ | ------------------ | ----------------- | --------------------------------------------------------------------------- | -| `warResources` | `Map[String,File]` | *src/main/webapp* | Static files (HTML, CSS, JS, images, etc.) to serve directly | -| `warClasses` | `Map[String,File]` | project classes | .class files to copy into the *WEB-INF/classes* directory | -| `warLib` | `Map[String,File]` | project libs | .jar files to copy into the *WEB-INF/lib* directory | -| `warRunnerVersion` | `String` | `"10.1.28.0-M1"` | The version of `com.earldouglas:webapp-components-runner` to run the webapp | -| `warPort` | `Int` | `8080` | The local container port to use when running with `warStart` | -| `warForkOptions` | [`ForkOptions`] | Buffered output | Options for the forked JVM used when running with `warStart` | +| Key | Type | Default | Notes | +| ------------------ | ------------------ | ------------------ | --------------------------------------------------------------------------- | +| `warResources` | `Map[String,File]` | *src/main/webapp* | Static files (HTML, CSS, JS, images, etc.) to serve directly | +| `warClasses` | `Map[String,File]` | project classes | .class files to copy into the *WEB-INF/classes* directory | +| `warLib` | `Map[String,File]` | project libs | .jar files to copy into the *WEB-INF/lib* directory | +| `warRunnerVersion` | `String` | `"10.1.28.0-M1"` | The version of `com.earldouglas:webapp-components-runner` to run the webapp | +| `warPort` | `Int` | `8080` | The local container port to use when running with `warStart` | +| `warForkOptions` | [`ForkOptions`] | [`BufferedOutput`] | Options for the forked JVM used when running with `warStart` | ## Commands @@ -305,3 +305,4 @@ This can be used to stop the running container: ``` [`ForkOptions`]: https://www.scala-sbt.org/1.x/api/sbt/ForkOptions.html +[`BufferedOutput`]: https://www.scala-sbt.org/1.x/api/sbt/OutputStrategy$$BufferedOutput.html From 6644bae85c6b67a4c578128f8abf513208c45757 Mon Sep 17 00:00:00 2001 From: James Earl Douglas Date: Tue, 15 Oct 2024 09:15:07 -0700 Subject: [PATCH 3/5] Copy edit --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 313e4a68..93532fd5 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,8 @@ warForkOptions := ### `warStart` and `warQuickstart` +To run the webapp, use `warStart`: + ``` > warStart ``` @@ -287,7 +289,7 @@ To skip packaging the .war file before launching the container, use ### `warJoin` -This can be used to block sbt while the container is running: +To block sbt while the container is running, use `warJoin`: ``` $ sbt warStart warJoin @@ -298,7 +300,7 @@ container), if you're into that kind of thing. ### `warStop` -This can be used to stop the running container: +To stop the running container, use `warStop`: ``` > warStop From e850b6b2bbe63ce1d5ffb34a1be70094ea777fd3 Mon Sep 17 00:00:00 2001 From: James Earl Douglas Date: Tue, 15 Oct 2024 09:15:53 -0700 Subject: [PATCH 4/5] Copy edit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 93532fd5..785e0801 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,8 @@ forked container JVM, set a [ForkOptions](https://www.scala-sbt.org/1.x/api/sbt/ForkOptions.html) instance via `warForkOptions`. -For example: to attach a debugger, set `-Xdebug` and `-Xrunjdwp`: +For example: to be able to attach a debugger, set `-Xdebug` and +`-Xrunjdwp`: *build.sbt:* From 0992dd4e7fd1d7051e1386e40d03cffe7b0f8f36 Mon Sep 17 00:00:00 2001 From: James Earl Douglas Date: Tue, 15 Oct 2024 09:16:58 -0700 Subject: [PATCH 5/5] Deduplicate imports --- src/main/scala/com/earldouglas/sbt/war/SbtWar.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala b/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala index e7b9816d..400082ec 100644 --- a/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala +++ b/src/main/scala/com/earldouglas/sbt/war/SbtWar.scala @@ -4,11 +4,9 @@ import sbt.AutoPlugin import sbt.Def.Initialize import sbt.Def.settingKey import sbt.Keys._ -import sbt.Keys._ import sbt.Keys.{`package` => pkg} import sbt.Plugins import sbt._ -import sbt._ import java.nio.file.Files import java.nio.file.Paths