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 19, 2024
1 parent 2df016c commit 03c0c8d
Show file tree
Hide file tree
Showing 27 changed files with 589 additions and 87 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
86 changes: 40 additions & 46 deletions src/main/scala/com/earldouglas/xwp/WebappPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object WebappPlugin extends AutoPlugin {

import autoImport._

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

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

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

val webappTargetDir: File =
webappTarget.value

val sourceFiles: Set[File] =
v5.V5WebappPlugin
.webappAssets(webappSrcDir)
.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)
t = IO.resolve(webappTargetDir, r)
} yield t
},
(webappSrcDir ** "*").get.toSet,
sourceFiles,
streams.value
)

webappTarget.value
webappTargetDir
}

private def webappPrepareQuickTask =
Expand All @@ -137,14 +148,12 @@ 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) {
def classesAsClasses(): Set[File] = {
// copy this project's classes directly to WEB-INF/classes
val m = v5.V5WebappPlugin.webappClasses.value
cacheify(
"classes",
{ in =>
Expand All @@ -161,50 +170,35 @@ object WebappPlugin extends AutoPlugin {
}).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),
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,
def classesAsJar(): Set[File] = {
// copy this project's classes as a .jar file in WEB-INF/lib
val jarFilename: String =
(packagedArtifact in packageBin in Compile).value._2.getName()

val outputJar = webappLibDir / jarFilename

Compat.jar(
sources = v5.V5WebappPlugin.webappClasses.value,
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.V5WebappPlugin.webappLib.value.keySet,
taskStreams
)

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

import java.util.jar.Manifest

import sbt._
import sbt.Def.taskKey
import sbt.Def.settingKey
import sbt.Keys._
import sbt.FilesInfo.lastModified
import sbt.FilesInfo.exists
import sbt.FileFunction.cached

object V5WebappPlugin extends AutoPlugin {

object autoImport {

lazy val Webapp =
config("webapp").hide

lazy val assets =
settingKey[Map[File, String]]("assets")

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

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

import autoImport._

override def requires = plugins.JvmPlugin

def webappAssets(srcDir: File): Map[File, String] = {
(srcDir ** "*").get
.filter(_.isFile())
.flatMap(src =>
IO
.relativize(srcDir, src)
.map(dst => src -> dst)
)
.toMap
}

val webappClasses: Def.Initialize[Task[Map[File, String]]] =
Def.task {

val classpathDirs: Seq[File] =
(fullClasspath in Runtime).value
.map(_.data)
.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
}

val webappLib: Def.Initialize[Task[Map[File, String]]] =
Def.task {
val cp: Keys.Classpath =
(fullClasspath in Runtime).value
cp
.map(_.data)
.filter(f => f.isFile())
.filter(f => f.getName().endsWith(".jar"))
.map(src => src -> src.getName())
.toMap
}

override def projectSettings: Seq[Setting[_]] =
inConfig(Webapp)(
Seq(
assets := {
val srcDir: File =
(sourceDirectory in Compile).value / "webapp"
webappAssets(srcDir)
},
classes := webappClasses.value.mapValues(d => s"classes/$d"),
lib := webappLib.value.mapValues(d => s"lib/$d")
)
)
}

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
14 changes: 14 additions & 0 deletions src/sbt-test/v5/webapp/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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(V5WebappPlugin)
enablePlugins(TomcatPlugin)

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

scalacOptions += "-Wunused:all"
42 changes: 42 additions & 0 deletions src/sbt-test/v5/webapp/check-assets.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
TaskKey[Unit]("check-assets") := {

def assertEquals(
name: String,
expected: Map[File, String],
obtained: Map[File, String]
): Unit = {

val sizesDoNotMatch = expected.size != obtained.size
val mappingsDoNotMatch = expected != obtained

if (sizesDoNotMatch || mappingsDoNotMatch) {
sys.error(
s"""|${name}:
| expected:
|${expected.mkString(" - ", "\n - ", "")}
| obtained:
|${obtained.mkString(" - ", "\n - ", "")}
|""".stripMargin
)
}
}

val expected: List[String] =
List(
"WEB-INF/web.xml",
"favicon.ico",
"index.html",
"styles/theme.css"
)

assertEquals(
name = "assets",
expected = {
val root: File = (Compile / sourceDirectory).value
expected
.map(x => root / "webapp" / x -> x)
.toMap
},
obtained = (Webapp / assets).value
)
}
Loading

0 comments on commit 03c0c8d

Please sign in to comment.