Skip to content

Commit

Permalink
WebappPlugin: Initial V5 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
earldouglas committed Sep 21, 2024
1 parent 16ea987 commit 3ff7b5d
Show file tree
Hide file tree
Showing 37 changed files with 691 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
version = 3.8.3 // https://scalameta.org/scalafmt/docs/installation.html#sbt
runner.dialect = scala212 // https://scalameta.org/scalafmt/docs/configuration.html#scala-dialects
runner.dialect = scala212source3 // https://scalameta.org/scalafmt/docs/configuration.html#scala-dialects
maxColumn = 72 // RFC 678: https://datatracker.ietf.org/doc/html/rfc678
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ semanticdbEnabled := true
semanticdbVersion := scalafixSemanticdb.revision
scalacOptions += "-Ywarn-unused-import"

// Testing
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % "test"

// Publish to Sonatype, https://www.scala-sbt.org/release/docs/Using-Sonatype.html
credentials := List(
Credentials(Path.userHome / ".sbt" / "sonatype_credentials")
Expand Down
115 changes: 57 additions & 58 deletions src/main/scala/com/earldouglas/xwp/WebappPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ object WebappPlugin extends AutoPlugin {

import autoImport._

override def requires = plugins.JvmPlugin
override def requires = v5.WebappPluginV5

override def projectSettings: Seq[Setting[_]] =
Seq(
Expand Down Expand Up @@ -94,22 +94,33 @@ object WebappPlugin extends AutoPlugin {
) =
Def.task {

val webappSrcDir = (sourceDirectory in webappPrepare).value
val webappResourcesDir: File =
(sourceDirectory in webappPrepare).value

val webappTargetDir: File =
webappTarget.value

val resourceFiles: Set[File] =
v5.WebappComponents
.getWebappResources(webappResourcesDir)
.filterNot(x => x._1.isDirectory())
.map(_._1)
.toSet

cacheify(
cacheName,
{ in =>
for {
f <- Some(in)
if !f.isDirectory
r <- IO.relativizeFile(webappSrcDir, f)
} yield IO.resolve(webappTarget.value, r)
r <- IO.relativizeFile(webappResourcesDir, f)
t = IO.resolve(webappTargetDir, r)
} yield t
},
(webappSrcDir ** "*").get.toSet,
resourceFiles,
streams.value
)

webappTarget.value
webappTargetDir
}

private def webappPrepareQuickTask =
Expand All @@ -136,74 +147,62 @@ object WebappPlugin extends AutoPlugin {
val webappTarget =
_webappPrepare(target in webappPrepare, "webapp").value

val m = (mappings in (Compile, packageBin)).value
val p = (packagedArtifact in (Compile, packageBin)).value._2

val webInfDir = webappTarget / "WEB-INF"
val webappLibDir = webInfDir / "lib"

if (webappWebInfClasses.value) {
// copy this project's classes directly to WEB-INF/classes
val classpath: Seq[File] =
(fullClasspath in Runtime).value
.map(_.data)

val webappClasses: Map[File, String] =
v5.WebappComponents.getWebappClasses(classpath)

// copy this project's classes directly to WEB-INF/classes
def classesAsClasses(): Set[File] = {

cacheify(
"classes",
{ in =>
m find { case (src, dest) =>
src == in
} map { case (src, dest) =>
webInfDir / "classes" / dest
}
webappClasses
.find { case (src, dest) => src == in }
.map { case (src, dest) => webInfDir / "classes" / dest }
},
(m filter { case (src, dest) =>
!src.isDirectory
} map { case (src, dest) =>
src
}).toSet,
taskStreams
)
} else {
// copy this project's classes as a .jar file in WEB-INF/lib
cacheify(
"lib-art",
{ in => Some(webappLibDir / in.getName) },
Set(p),
webappClasses
.filter { case (src, dest) => !src.isDirectory }
.map { case (src, dest) => src }
.toSet,
taskStreams
)
}

val classpath = (fullClasspath in Runtime).value

// create .jar files for depended-on projects in WEB-INF/lib
for {
cpItem <- classpath.toList
dir = cpItem.data
if dir.isDirectory
artEntry <- cpItem.metadata.entries find { e =>
e.key.label == "artifact"
}
cpArt = artEntry.value.asInstanceOf[Artifact]
artifact = (packagedArtifact in (Compile, packageBin)).value._1
if cpArt != artifact
files = (dir ** "*").get flatMap { file =>
if (!file.isDirectory)
IO.relativize(dir, file) map { p => (file, p) }
else
None
}
jarFile = cpArt.name + ".jar"
_ = Compat.jar(
sources = files,
outputJar = webappLibDir / jarFile,
// copy this project's classes as a .jar file in WEB-INF/lib
def classesAsJar(): Set[File] = {

val jarFilename: String =
(packagedArtifact in packageBin in Compile).value._2.getName()

val outputJar = webappLibDir / jarFilename

Compat.jar(
sources = webappClasses,
outputJar = outputJar,
manifest = new Manifest
)
} yield ()

Set(outputJar)
}

if (webappWebInfClasses.value) {
classesAsClasses()
} else {
classesAsJar()
}

// copy this project's library dependency .jar files to WEB-INF/lib
cacheify(
"lib-deps",
{ in => Some(webappTarget / "WEB-INF" / "lib" / in.getName) },
classpath.map(_.data).toSet filter { in =>
!in.isDirectory && in.getName.endsWith(".jar")
},
{ in => Some(webappTarget / "WEB-INF" / "lib" / in.getName()) },
v5.WebappComponents.getWebappLib(classpath).keySet,
taskStreams
)

Expand Down
62 changes: 62 additions & 0 deletions src/main/scala/v5/WebappComponents.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package v5

import sbt._

object WebappComponents {

/** Given a resources directory, e.g. src/main/webapp, traverse to
* find all the files it contains.
*
* @return
* a mapping from source to destination of webapp resources
*/
def getWebappResources(resourcesDir: File): Map[File, String] = {
(resourcesDir ** "*").get
.filter(_.isFile())
.flatMap(src =>
IO
.relativize(resourcesDir, src)
.map(dst => src -> dst)
)
.toMap
}

/** Given a classpath (potentially with both .jar files and classes
* directories), traverse to find all the .class files.
*
* @return
* a mapping from source to destination of .class files
*/
def getWebappClasses(classpath: Seq[File]): Map[File, String] = {

val classpathDirs: Seq[File] =
classpath
.filter(_.isDirectory())

val classesMappings: Seq[(File, File)] =
for {
classpathDir <- classpathDirs
classFile <- (classpathDir ** "*").get
if classFile.isFile()
relativeFile <- IO.relativizeFile(classpathDir, classFile)
} yield (classFile, relativeFile)

classesMappings
.map({ case (src, dst) => src -> dst.getPath() })
.toMap
}

/** Given a classpath (potentially with both .jar files and classes
* directories), traverse to find all the .jar files.
*
* @return
* a mapping from source to destination of .jar files
*/
def getWebappLib(classpath: Seq[File]): Map[File, String] = {
classpath
.filter(f => f.isFile())
.filter(f => f.getName().endsWith(".jar"))
.map(src => src -> src.getName())
.toMap
}
}
49 changes: 49 additions & 0 deletions src/main/scala/v5/WebappPluginV5.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package v5

import sbt.Def.settingKey
import sbt.Def.taskKey
import sbt.Keys._
import sbt._

/** Finds webapp components (resources, .class files, and .jar files).
* This is used by WarPlugin and WebappRunnerPlugin.
*/
object WebappPluginV5 extends AutoPlugin {

object autoImport {

lazy val webappResources =
settingKey[Map[File, String]]("webapp resources")

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

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

import autoImport._

override def requires = plugins.JvmPlugin

override def projectSettings: Seq[Setting[_]] =
Seq(
webappResources := {
val srcDir: File =
(sourceDirectory in Compile).value / "webapp"
WebappComponents.getWebappResources(srcDir)
},
webappClasses := {
val classpath: Seq[File] =
(fullClasspath in Runtime).value
.map(_.data)
WebappComponents.getWebappClasses(classpath)
},
webappLib := {
val classpath: Seq[File] =
(fullClasspath in Runtime).value
.map(_.data)
WebappComponents.getWebappLib(classpath)
}
)
}

This file was deleted.

11 changes: 2 additions & 9 deletions src/sbt-test/container/multi-module-multi-webapp/test
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,13 @@

> jetty:stop

$ exists mathsweb/target/webapp/WEB-INF/lib/maths.jar
$ absent mathsweb/target/webapp/WEB-INF/lib/remote.jar
$ absent mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war

$ exists remoteweb/target/webapp/WEB-INF/lib/remote.jar
$ absent remoteweb/target/webapp/WEB-INF/lib/maths.jar
$ absent remoteweb/target/scala-2.12/remoteweb_2.12-0.1.0-SNAPSHOT.war

> package

$ exists mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war
> findInZip mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/maths.jar
-> findInZip mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/remote.jar
> findInZip mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/mathsweb_2.12-0.1.0-SNAPSHOT.jar

$ exists remoteweb/target/scala-2.12/remoteweb_2.12-0.1.0-SNAPSHOT.war
> findInZip remoteweb/target/scala-2.12/remoteweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/remote.jar
-> findInZip remoteweb/target/scala-2.12/remoteweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/maths_2.12-0.1.0-SNAPSHOT.jar
> findInZip remoteweb/target/scala-2.12/remoteweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/remoteweb_2.12-0.1.0-SNAPSHOT.jar
7 changes: 1 addition & 6 deletions src/sbt-test/container/multi-module-single-webapp/test
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
> get http://localhost:8080/index.html 200
> get http://localhost:8080/test 200
> jetty:stop
$ exists mathsweb/target/webapp/WEB-INF/lib/numbers.jar
$ exists mathsweb/target/webapp/WEB-INF/lib/maths.jar
$ exists mathsweb/target/webapp/WEB-INF/lib/typeclasses.jar
$ exists mathsweb/target/webapp/WEB-INF/lib/mathsweb_2.12-0.1.0-SNAPSHOT.jar
$ absent target/scala-2.12/root_2.12-0.1.0-SNAPSHOT.war
$ absent numbers/target/scala-2.12/numbers_2.12-0.1.0-SNAPSHOT.war
$ absent typeclasses/target/scala-2.12/typeclasses_2.12-0.1.0-SNAPSHOT.war
Expand All @@ -18,6 +16,3 @@ $ absent typeclasses/target/scala-2.12/typeclasses_2.12-0.1.0-SNAPSHOT.war
$ absent maths/target/scala-2.12/maths_2.12-0.1.0-SNAPSHOT.war
$ exists mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war
> findInZip mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/mathsweb_2.12-0.1.0-SNAPSHOT.jar
> findInZip mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/numbers.jar
> findInZip mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/maths.jar
> findInZip mathsweb/target/scala-2.12/mathsweb_2.12-0.1.0-SNAPSHOT.war WEB-INF/lib/typeclasses.jar
4 changes: 4 additions & 0 deletions src/sbt-test/v5/webapp/.scalafix.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
rules = [
OrganizeImports
RemoveUnused
]
2 changes: 2 additions & 0 deletions src/sbt-test/v5/webapp/.scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
version=3.7.13
runner.dialect=scala3
13 changes: 13 additions & 0 deletions src/sbt-test/v5/webapp/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.4"
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % "test"

libraryDependencies += "com.h2database" % "h2" % "2.2.224"
libraryDependencies += "javax.servlet" % "javax.servlet-api" % "4.0.1" % "provided"

enablePlugins(WebappPluginV5)

scalaVersion := "3.5.0"
semanticdbEnabled := true
semanticdbVersion := scalafixSemanticdb.revision

scalacOptions += "-Wunused:all"
Loading

0 comments on commit 3ff7b5d

Please sign in to comment.