I want to combine Java/Scala sbt subprojects in a way that each module is a self-contained SPA micro-service. I am constrained to Spring Boot (Tomcat) to serve the files for historical reasons. I chose Scala.js to write the Javascript client side. The packaging is done with the help of sbt plugins. The relevant part of build.sbt is:
ThisBuild / scalaVersion := "2.12.6"
lazy val iamProject = ProjectRef(uri("https://github.com/iservport/iservport-iam.git"), "iam")
lazy val appCargo = (project in file("app-cargo")).enablePlugins(ScalaJSPlugin, ScalaJSWeb)
lazy val root = (project in file("."))
.enablePlugins(JavaServerAppPackaging, UniversalDeployPlugin, AshScriptPlugin)
.enablePlugins(DockerPlugin, SbtWeb)
.settings(
scalaJSProjects := Seq(appCargo),
pipelineStages in Assets := Seq(scalaJSPipeline),
name := "iservport-control",
mainClass in Compile := Some("com.iservport.Application"),
...
).dependsOn(iamProject, appCargo)
When I expand the application zip generated by universal:packageBin, under the lib directory, I can find com.iservport.iservport-cargo-1.1.1.RELEASE.jar (the module), and:
jar -tf com.iservport.iservport-control-1.1.1.RELEASE.jar | grep cargo
…
META-INF/resources/webjars/iservport-control/1.1.1.RELEASE/14848cb02339ea90f0c6/com/iservport/cargo/service/ShipmentService.scala
META-INF/resources/webjars/iservport-control/1.1.1.RELEASE/iservport-cargo-opt.js.map
META-INF/resources/webjars/iservport-control/1.1.1.RELEASE/14848cb02339ea90f0c6/com/iservport/cargo/service/ShipmentDocumentService.scala
META-INF/resources/webjars/iservport-control/1.1.1.RELEASE/iservport-cargo-opt.js
META-INF/resources/webjars/iservport-control/1.1.1.RELEASE/14848cb02339ea90f0c6/com/iservport/cargo/repository/ShipmentTypeRepository.scala
…
I tested Spring Boot ability to serve webjars, for example, d3.js, and I see it working. However, I can't see the same webjar mapping work for a similar resource inside my jar:
META-INF/resources/webjars/iservport-control/1.1.1.RELEASE/iservport-cargo-opt.js
I've tried with localhost:8443/webjars/iservport-control/1.1.1.RELEASE/iservport-cargo-opt.js , localhost:8443/webjars/iservport-control /iservport-cargo-opt.js and other variants, they all are 404.
How can I expose the above iservport-cargo-opt.js to the client?
After digging into the Scala.js docs, I found out the solution:
localhost:8443/webjars/iservport-control/1.1.1.RELEASE/iservport-cargo-fastopt.js
I was testing with a local instance, created using fastOptJS, but in production ScalaJs uses fullOptJS.
Related
I'm trying to create a relatively simple sbt plugin to wrap grpc-swagger artifact.
Therefore, I've created a project with the following structure:
projectDir/
build.sbt
lib/grpc-swagger.jar <- the artifact I've downloaded
src/...
where build.sbt looks like the following:
ThisBuild / version := "0.0.1-SNAPSHOT"
ThisBuild / organization := "org.testPlugin"
ThisBuild / organizationName := "testPlugin"
lazy val root = (project in file("."))
.enable(SbtPlugin)
.settings(name := "grpc-swagger-test-plugin")
According to sbt docs, that's all I have to do in order to include an unmanaged dependecy, that is:
create a lib folder;
store the artifact in there;
However, when I do execute sbt compile publishLocal, the plugin published lacks of that external artifact.
So far I've tried to:
set exportJars := true flag
add Compile / unmanagedJars += file(lib/grpc-swagger.jar") (with also variations of the path)
manual fiddling to libraryDependecies using from file("lib/grpc-swagger.jar") specifier
but none so far seemed to work.
So how am I supposed to add an external artifact to a sbt plugin?
The proper solution to this problem is to publish the grpc-swagger library. If for whatever reason this can't be done from that library's build system, you can do it with sbt. Just add a simple subproject whose only job it is to publish that jar. It should work like so:
...
lazy val `grpc-swagger` = (project in file("."))
.settings(
name := "grpc-swagger",
Compile / packageBin := baseDirectory.value / "lib" / "grpc-swagger.jar",
// maybe other settings, such as grpc-swagger's libraryDependencies
)
lazy val root = (project in file("."))
.enable(SbtPlugin)
.settings(name := "grpc-swagger-test-plugin")
.dependsOn(`grpc-swagger`)
...
The pom file generated for the root project should now specify a dependency on grpc-swagger, and running the publish task in the grpc-swagger project will publish that jar along with a pom file.
That should be enough to make things work, but honestly, it's still a hack. The proper solution is to fix grpc-swagger's build system so you can publish an artifact from there and then just use it via libraryDependencies.
I have a multi-project build.sbt file, with projects like so:
lazy val utils = (project in file("utils"))
.settings(
Seq(
publishArtifact := false
)).[...]
lazy val api = (project in file("api"))
.dependsOn(utils)
.settings(commonSettings: _*)
.enablePlugins(JavaAppPackaging, DockerPlugin)
.settings(publish := {})
.settings(
Seq(
packageName in Docker := "my-api",
dockerBaseImage := "java:8",
mainClass in Compile := Some("com.path.to.Main"),
publishArtifact := false,
unmanagedJars in Compile += file("jars/somejars.jar")
))
API is built on top of Finch framework. I create a docker image for the API using sbt api/docker:publishLocal and then run it locally. However, it seems like the utils subproject classes are not packaged with the final container, and as a result I am getting multiple
java.lang.ClassNotFoundException:
types of exceptions. For a similar project that doesn't have a subproject dependency, everything runs smoothly and I have no problems.
Am I missing something in the plugin configuration? I thought .dependsOn() should be taking care of providing dependent classes in the project docker image.
Answering my own question, but turns out this is a default behaviour of sbt-native-packager, or rather sbt, when a dependent project has publishArtifact := false setting.
A workaround that worked for me was changing the above to publish/skip := true.
More on this issue can be found here: https://github.com/sbt/sbt-native-packager/issues/1221
Following the hints of the post explaining the basics of migrating to scalajs and this page about cross-compilations, I decided to add cross compilation to my standalone dependency-free scala library by doing the following changes:
I added a file project/plugins.sbt with the content
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.16")
I added scalaVersion in ThisBuild := "2.11.8" in build.sbt because else just scalaVersion was using 2.10
I also added in the build.sbt the following content to ensure that I can keep the same directory structure, since I don't have any particular files for the JVM or for Javascript:
lazy val root = project.in(file(".")).
aggregate(fooJS, fooJVM).
settings(
publish := {},
publishLocal := {}
)
lazy val foo = crossProject.crossType(CrossType.Pure).in(file(".")).
settings(version := "0.1").
jvmSettings(
// Add JVM-specific settings here
).
jsSettings(
// Add JS-specific settings here
)
lazy val fooJVM = foo.jvm
lazy val fooJS = foo.js
But now, after I published the project locally using sbt publish-local the projects depending on this library do not work anymore, i.e. they don't see the classes that this library was offering and raise errors.
I looked into .ivy2/local/.../foo/0.1/jars and the JAR went from 1MB to 1KB, so the errors make sense.
However, how can I make sure the JVM jar file is compiled correctly?
Further informations
The jar time does not change anymore, it looks like there had been some miscompilation. I deleted the .ivy2 cache, but now sbt publish-local always finishes with success but does not regenerate the files.
Ok I found the solution myself.
I needed to remove the publishLocal := {} from the build, and now all the projects depending on my library work fine.
I've been tasked with rewriting an old ant build script to SBT. As it happens, our suite is built up of 3 modules:
A Play 2.3 front-end webserver;
A back-end for retrieving data from various other systems;
A middle module containing some shared classes for database access and business logic.
Below an excerpt of my Build.scala file can be found:
val sharedSettings = Seq(
organization := <organization here>,
version := "1.2.5",
scalaVersion := "2.11.1",
libraryDependencies ++= libraries,
unmanagedJars in Compile ++= baseDirectory.value / "lib",
unmanagedJars in Compile ++= baseDirectory.value / "src",
unmanagedJars in Compile ++= baseDirectory.value / "test"
)
lazy val middle = project.settings(sharedSettings: _*)
lazy val back = project.settings(sharedSettings: _*).dependsOn(middle)
However, when I try to compile the source, I get the following error:
bad symbolic reference to scala.reflect.runtime encountered in class file 'ValueConverter.class'. Cannot access term runtime in package scala.reflect. The current classpath may be missing a definition for scala.reflect.runtime, or ValueConverter.class may have been compiled against a version that's incompatible with the one found on the current classpath.
The source code is organized in the following structure:
back
src
test
lib
middle
src
test
lib
front
src
test
lib
Here each lib folder contains some manually maintained libraries (which is why we want to move to sbt).
Any ideas on how to solve this?
In the end, I gave up on trying to get the compiler to understand the additional libraries. Eventually, I added those dependencies that were available using sbt, to the sbt managed libraries. This apparently works well.
I need to build a single jar, including dependencies, for one of my sub-projects so that it can be used as a javaagent.
I have a multi-module sbt project and this particular module is the lowest level one (it's also pure Java).
Can I (e.g. with sbt-onejar, sbt-proguard or sbt assembly) override how the lowest level module is packaged?
It looks like these tools are really designed to be a post-publish step, but I really need a (replacement or additional) published artefact to include the dependencies (but only for this one module).
UPDATE: Publishing for sbt-assembly are instructions for a single project, and doesn't easily translate into multi-project.
Publishing for sbt-assembly are instructions for a single project, and doesn't easily translate into multi-project.
People have been publishing fat JAR using sbt-assembly & sbt-release without issues. Here's a blog article from 2011: Publishing fat jar created by sbt-assembly. It boils down to adding addArtifact(Artifact(projectName, "assembly"), sbtassembly.AssemblyKeys.assembly) to your build.sbt (note that the blog is a little out of date AssemblyKeys is now a member of sbtassembly directly).
For sbt 0.13 and above, I prefer to use build.sbt for multi-projects too, so I'd write it like:
import AssemblyKeys._
lazy val commonSettings = Seq(
version := "0.1-SNAPSHOT",
organization := "com.example",
scalaVersion := "2.10.1"
)
val app = (project in file("app")).
settings(commonSettings: _*).
settings(assemblySettings: _*).
settings(
artifact in (Compile, assembly) ~= { art =>
art.copy(`classifier` = Some("assembly"))
}
).
settings(addArtifact(artifact in (Compile, assembly), assembly).settings: _*)
See Defining custom artifacts:
addArtifact returns a sequence of settings (wrapped in a SettingsDefinition). In a full build configuration, usage looks like:
...
lazy val proj = Project(...)
.settings( addArtifact(...).settings : _* )
...