How to use custom plugin in multi-module project? - scala

In a multi-module project, with one module implementing a custom SBT plugin with custom TaskKey, how this plugin can be imported for the project settings of another submodule.
If the submodule using the plugin is define in submodule1/build.sbt, then submodule1/project/plugins.sbt is not loaded.
If plugin is registered in project/plugins.sbt it will fails when loading top/aggregate project as plugin is not necessarily already built.
Is there any other way to define a custom task requiring custom dependency so that it can be used by a submodule?

Here is how I finally make it works:
import sbt._
import Keys._
object MyBuild extends Build {
private lazy val myGenerator =
// Private project with generator code and its specific dependencies
// (e.g. Javassist)
Project(id = "my-generator",
base = file("project") / "my-generator").settings(
name := "my-generator",
javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
autoScalaLibrary := false,
scalacOptions += "-feature",
resolvers +=
"Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/releases/",
libraryDependencies ++= Seq( // Dependencies required to generate classes
"org.javassist" % "javassist" % "3.18.2-GA")
)
// Some custom setting & task
lazy val generatedClassDirectory = settingKey[File](
"Directory where classes get generated")
lazy val generatedClasses = taskKey[Seq[(File, String)]]("Generated classes")
lazy val myProject =
Project(id = "my-project", base = file("my-project")).settings(
name := "my-project",
javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
autoScalaLibrary := false,
scalacOptions += "-feature",
libraryDependencies ++= Seq(/* ... */),
generatedClassDirectory := {
// Defines setting for path to generated classes
val dir = target.value / "generated_classes"
if (!dir.exists) dir.mkdirs()
dir
},
generatedClasses <<= Def.task { // Define task generating .class files
// first get classloader including generator and its dependencies
val cp = (fullClasspath in (myGenerator, Compile)).value
val cl = classpath.ClasspathUtilities.toLoader(cp.files)
// then loaded generator class, and instantiate with structural type
val genClass = cl loadClass "my.custom.GeneratorClass"
val generator = genClass.newInstance.
asInstanceOf[{def writeTo(out: File): File}]
// finally we can call the
val outdir = generatedClassDirectory.value
val generated = generator writeTo outdir
val path = generated.getAbsolutePath
// Mappings describing generated classes
Seq[(File, String)](generated -> path.
drop(outdir.getAbsolutePath.length+1))
} dependsOn(compile in (myGenerator, Compile))/* awkward? */,
managedClasspath in Compile := {
// Add generated classes to compilation classpath,
// so it can be used in my-project sources
val cp = (managedClasspath in Compile).value
cp :+ Attributed.blank(generatedClassDirectory.value)
},
// Make sure custom class generation is done before compile
compile in Compile <<= (compile in Compile) dependsOn generatedClasses,
mappings in (Compile, packageBin) := {
val ms = mappings.in(Compile, packageBin).value
ms ++ generatedClasses.value // add generated classes to package
}
).dependsOn(myGenerator/* required even if there dependsOn(compile in (myGenerator, Compile)) */)
}
Not sure there is better solution, especially about the redondant dependsOn(compile in (myGenerator, Compile)) and .dependsOn(myGenerator).

Related

how to run scala sbt-native-packager for a appJS/appJVM cross-build project

The sbt-native-packager can make a zip file with all dependencies and a script to run_
$ sbt universal:packageBin
I have a scala web application, using cross-build (appJS for front-end and appJVM for back-end).
How do I run this packager for the appJVM?
I've tried as follows, but it does not accept the command:
$ sbt appJVM/universal:packageBin
Here it is the build.sbt project, from https://www.scala-js.org/doc/project/cross-build.html
...
lazy val foo = crossProject.in(file(".")).
settings(
name := "foo",
version := "0.1-SNAPSHOT"
).
jvmSettings(
// Add JVM-specific settings here
).
jsSettings(
// Add JS-specific settings here
)
lazy val fooJVM = foo.jvm
lazy val fooJS = foo.js
How do I run this packager for the appJVM?
And how I include the file generated by sbt appJS/fullOptJS?
And some other static files?
Update with Ivan response
build.sbt:
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
val sharedSettings = Seq(
scalaVersion := "2.12.8",
)
lazy val app =
crossProject(JSPlatform, JVMPlatform)
.in(file("."))
.settings(sharedSettings)
.jsSettings(
)
.jvmSettings(
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % "10.1.9"
),
)
lazy val backend = project
.enablePlugins(UniversalPlugin)
.enablePlugins(JavaAppPackaging)
.dependsOn(app.jvm)
.settings(
mainClass in Compile := Some("com.example.EchoServer")
)
lazy val frontend = project
.enablePlugins(ScalaJSPlugin)
.dependsOn(app.js)
backend
.settings(
Seq(
resourceGenerators in Compile += Def.task {
Seq(
(fullOptJS in Compile in frontend).value,
(fastOptJS in Compile in frontend).value
).map { js =>
val resource = (resourceManaged in Compile).value / "public" / "assets" / js.data.name
IO.write(resource, IO.read(js.data))
resource
}
}.taskValue
)
)
and run:
$ sbt backend/universal:packageBin
34: error: type mismatch;
found : Seq[sbt.Def.Setting[Seq[sbt.Task[Seq[java.io.File]]]]]
required: Int
Seq(
^
[error] Type error in expression
I used the following structure.
Define a shared project that needs to be cross-compiled for JS and Scala.
lazy val shared = CrossPlugin.autoImport
.crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.jvmSettings(???)
.jsSettings(???)
lazy val sharedJvm = shared.jvm
lazy val sharedJs = shared.js
Add project that contains a Main class.
lazy val backend = project
.enablePlugins(UniversalPlugin)
.enablePlugins(JavaAppPackaging)
.dependsOn(sharedJvm)
Add web project containing web related code.
lazy val web = project
.enablePlugins(ScalaJSPlugin)
.dependsOn(sharedJs)
And finally, attach resources from web compiled into JS to backend.
backend
.settings(
Seq(
resourceGenerators in Compile += Def.task {
Seq(
(fullOptJS in Compile in web).value,
(fastOptJS in Compile in web).value
).map { js =>
val resource = (resourceManaged in Compile).value / "public" / "assets" / js.data.name
IO.write(resource, IO.read(js.data))
resource
}
}.taskValue
)
Main class needs to service compiled JS from public/assets, as configured in sbt, and any other web resources from its class path.

How to have sbt multi-project builds configure setting for subprojects?

I have an sbt (0.13.1) project with a bunch of subprojects. I am generating eclipse project configurations using sbteclipse. My projects only have scala source files, so I want to remove the generated src/java folders.
I can achieve that by (redundantly) adding the following to the build.sbt of each subproject:
unmanagedSourceDirectories in Compile := (scalaSource in Compile).value :: Nil
unmanagedSourceDirectories in Test := (scalaSource in Test).value :: Nil
I tried just adding the above configuration to the root build.sbt but the eclipse command still generated the java source folders.
Is there any way to specify a configuration like this once (in the root build.sbt) and have it flow down to each subproject?
You could define the settings unscoped and then reuse them
val onlyScalaSources = Seq(
unmanagedSourceDirectories in Compile := Seq((scalaSource in Compile).value),
unmanagedSourceDirectories in Test := Seq((scalaSource in Test).value)
)
val project1 = project.in( file( "project1" )
.settings(onlyScalaSources: _*)
val project2 = project.in( file( "project2" )
.settings(onlyScalaSources: _*)
You could also create a simple plugin (untested code)
object OnlyScalaSources extends AutoPlugin {
override def trigger = allRequirements
override lazy val projectSettings = Seq(
unmanagedSourceDirectories in Compile := Seq((scalaSource in Compile).value),
unmanagedSourceDirectories in Test := Seq((scalaSource in Test).value)
)
}
More details about creating plugins in the plugins documentation

Compile with different settings in different commands

I have a project defined as follows:
lazy val tests = Project(
id = "tests",
base = file("tests")
) settings(
commands += testScalalib
) settings (
sharedSettings ++ useShowRawPluginSettings ++ usePluginSettings: _*
) settings (
libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-reflect" % _),
libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-compiler" % _),
libraryDependencies += "org.tukaani" % "xz" % "1.5",
scalacOptions ++= Seq()
)
I would like to have three different commands which will compile only some files inside this project. The testScalalib command added above for instance is supposed to compile only some specific files.
My best attempt so far is:
lazy val testScalalib: Command = Command.command("testScalalib") { state =>
val extracted = Project extract state
import extracted._
val newState = append(Seq(
(sources in Compile) <<= (sources in Compile).map(_ filter(f => !f.getAbsolutePath.contains("scalalibrary/") && f.name != "Typers.scala"))),
state)
runTask(compile in Compile, newState)
state
}
Unfortunately when I use the command, it still compiles the whole project, not just the specified files...
Do you have any idea how I should do that?
I think your best bet would be to create different configurations like compile and test, and have the appropriate settings values that would suit your needs. Read Scopes in the official sbt documentation and/or How to define another compilation scope in SBT?
I would not create additional commands, I would create an extra configuration, as #JacekLaskowski suggested, and based on the answer he had cited.
This is how you can do it (using Sbt 0.13.2) and Build.scala (you could of course do the same in build.sbt, and older Sbt version with different syntax)
import sbt._
import Keys._
object MyBuild extends Build {
lazy val Api = config("api")
val root = Project(id="root", base = file(".")).configs(Api).settings(custom: _*)
lazy val custom: Seq[Setting[_]] = inConfig(Api)(Defaults.configSettings ++ Seq(
unmanagedSourceDirectories := (unmanagedSourceDirectories in Compile).value,
classDirectory := (classDirectory in Compile).value,
dependencyClasspath := (dependencyClasspath in Compile).value,
unmanagedSources := {
unmanagedSources.value.filter(f => !f.getAbsolutePath.contains("scalalibrary/") && f.name != "Typers.scala")
}
))
}
now when you call compile everything will get compiled, but when you call api:compile only the classes matching the filter predicate.
Btw. You may want to also look into the possibility of defining different unmanagedSourceDirectories and/or defining includeFilter.

SBT: How to make one task depend on another in multi-project builds, and not run in the root project?

For my multi-project build, I'm trying to create a verify task that just results in scct:test and then scalastyle being executed in order. I would like scct:test to execute for all the subprojects, but not the top-level project. (If it executes for the top-level project, I get "timed out waiting for coverage report" from scct, since there's no source and no tests in that project.) What I had thought to do was to create verify as a task with dependencies on scct:test and scalastyle. This has turned out to be fairly baroque. Here is my Build.scala from my top-level project/ directory:
object MyBuild extends Build {
val verifyTask = TaskKey[Unit]("verify", "Compiles, runs tests via scct:test and then runs scalastyle")
val scctTestTask = (test in ScctPlugin.Scct).scopedKey
val scalastyleTask = PluginKeys.scalastyleTarget.scopedKey
lazy val root = Project("rootProject",
file("."),
settings = Defaults.defaultSettings ++
ScalastylePlugin.Settings ++
ScctPlugin.instrumentSettings ++
ScctPlugin.mergeReportSettings ++
Seq(
verifyTask in Global := {},
verifyTask <<= verifyTask.dependsOn(scctTestTask, scalastyleTask)
)
) aggregate(lift_webapp, selenium_tests)
lazy val subproject_1 = Project(id = "subproject_1", base = file("subproject_1"))
lazy val subproject_2 = Project(id = "subproject_2", base = file("subproject_2"))
}
However, the verify task only seems to exist for the root project; when I run it I don't see the same task being run in the subprojects. This is exactly the opposite of what I want; I'd like to issue sbt verify and have scct:test and scalastyle run in each of the subprojects but not in the top-level project. How might I go about doing that?
solution 1: define verifyTask in subprojects
First thing to note is that if you want some task (verify, test, etc) to run in some projects, you need to define them scoped to the subprojects. So in your case, the most straightforward thing to do this is to define verifyTask in subproject_1 and subproject_2.
lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.4"
lazy val verify = taskKey[Unit]("verify")
def verifySettings = Seq(
skip in verify := false,
verify := (Def.taskDyn {
val sk = (skip in verify).value
if (sk) Def.task { println("skipping verify...") }
else (test in Test)
}).value
)
lazy val root = (project in file("."))
.aggregate(sub1, sub2)
.settings(
verifySettings,
scalaVersion in ThisBuild := "2.12.4",
skip in verify := true
)
lazy val sub1 = (project in file("sub1"))
.settings(
verifySettings,
libraryDependencies += scalaTest % Test
)
lazy val sub2 = (project in file("sub2"))
.settings(
verifySettings,
libraryDependencies += scalaTest % Test
)
solution 2: ScopeFilter
There was a recent Reddit thread that mentioned this question, so I'll post what I've done there.
If you want to manually aggregate on some subprojects, there's also a technique called ScopeFilter.
Note that I am using sbt 1.x here, but it should work with sbt 0.13 some minor change.
lazy val packageAll = taskKey[Unit]("package all the projects")
lazy val myTask = inputKey[Unit]("foo")
lazy val root = (project in file("."))
.aggregate(sub1, sub2)
.settings(
scalaVersion in ThisBuild := "2.12.4",
packageAll := {
(packageBin in Compile).all(nonRootsFilter).value
()
},
myTask := {
packageAll.value
}
)
lazy val sub1 = (project in file("sub1"))
lazy val sub2 = (project in file("sub2"))
def nonRootsFilter = {
import sbt.internal.inc.ReflectUtilities
def nonRoots: List[ProjectReference] =
allProjects filter {
case LocalProject(p) => p != "root"
case _ => false
}
def allProjects: List[ProjectReference] =
ReflectUtilities.allVals[Project](this).values.toList map { p =>
p: ProjectReference
}
ScopeFilter(inProjects(nonRoots: _*), inAnyConfiguration)
}
In the above, myTask depends on packageAll, which aggregates (packageBin in Compile) for all non-root subprojects.
sbt:root> myTask
[info] Packaging /Users/xxx/packageall/sub1/target/scala-2.12/sub1_2.12-0.1.0-SNAPSHOT.jar ...
[info] Done packaging.
[info] Packaging /Users/xxx/packageall/sub2/target/scala-2.12/sub2_2.12-0.1.0-SNAPSHOT.jar ...
[info] Done packaging.
[success] Total time: 0 s, completed Feb 2, 2018 7:23:23 PM
I may be wrong, but you are defining the verify task dependency only for the current project.
Maybe you can try:
Seq(
verifyTask in Global := {},
verifyTask <<= (verifyTask in Global).dependsOn(scctTestTask, scalastyleTask)
)
Or you can add the verifyTask settings to all your modules.

How to get list of dependency jars from an sbt 0.10.0 project

I have a sbt 0.10.0 project that declares a few dependencies somewhat like:
object MyBuild extends Build {
val commonDeps = Seq("commons-httpclient" % "commons-httpclient" % "3.1",
"commons-lang" % "commons-lang" % "2.6")
val buildSettings = Defaults.defaultSettings ++ Seq ( organization := "org" )
lazy val proj = Project("proj", file("src"),
settings = buildSettings ++ Seq(
name := "projname",
libraryDependencies := commonDeps, ...)
...
}
I wish to creat a build rule to gather all the jar dependencies of "proj", so that I can symlink them to a single directory.
Thanks.
Example SBT task to print full runtime classpath
Below is roughly what I'm using. The "get-jars" task is executable from the SBT prompt.
import sbt._
import Keys._
object MyBuild extends Build {
// ...
val getJars = TaskKey[Unit]("get-jars")
val getJarsTask = getJars <<= (target, fullClasspath in Runtime) map { (target, cp) =>
println("Target path is: "+target)
println("Full classpath is: "+cp.map(_.data).mkString(":"))
}
lazy val project = Project (
"project",
file ("."),
settings = Defaults.defaultSettings ++ Seq(getJarsTask)
)
}
Other resources
Unofficial guide to sbt 0.10.
Keys.scala defines predefined keys. For example, you might want to replace fullClasspath with managedClasspath.
This plugin defines a simple command to generate an .ensime file, and may be a useful reference.