How to (automatically) inherit settings/tasks from an sbt plugin? - scala

I have an sbt plugin defining tasks that I would like to have available in a Play project, or another sbt project in general. While it might not be best practice, I'd prefer to have these tasks automatically available in the Play project, so that all I need to do is add the sbt plugin via plugins.sbt. But before I can even get that far, I'm having trouble importing tasks at all.
If the plugin's build.sbt is as follows:
name := "sbt-task-test"
version := "1.0.0-SNAPSHOT"
scalaVersion := "2.10.3"
scalaBinaryVersion := "2.10"
organization := "com.example"
sbtPlugin := true
lazy val testTask = taskKey[Unit]("Run a test task.")
testTask := {
println("Running test task..")
}
How can I make testTask available in another sbt project's build.sbt or Build.scala? I've tried following this example to no avail.
My end goal is to use tasks defined like in this blog post, but I'd like to at least get some simpler examples working first. In this case, I'd be adding something like registerTask("testTask", "com.example.tasks.Test", "Run a test task") to build.sbt, however I have the same problem as above.

First, you should put your task definition in the source of the plugin, not the build.sbt. So try this:
build.sbt of the plugin (it defines only how to build the plugin):
name := "sbt-task-test"
version := "1.0.0-SNAPSHOT"
scalaVersion := "2.10.3"
// scalaBinaryVersion := "2.10" // better not to play with this
organization := "com.example"
sbtPlugin := true
src/main/scala/MyPlugin.scala (in the plugin project)
import sbt._
object MyPlugin extends Plugin {
lazy val testTask = taskKey[Unit]("Run a test task.")
override def settings = Seq(
testTask := { println("Running test task..") }
)
}
Overriding settings helps to add the definition of this task to the project scope.
Now you should build and publish the plugin (locally for example) using sbt publishLocal.
Then in the project, where you want to use this plugin:
project/plugins.sbt should contain:
addSbtPlugin("com.example" % "sbt-task-test" % "1.0.0-SNAPSHOT")
This will add testTask key and definition to the scope automatically, so that you can do in the project's directory:
sbt testTask
and it will print Running test task..

Related

sbt plugin: add an unmanaged jar file

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.

Sbt generated docker container fails to package subproject

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

Intertwined dependencies between sbt plugin and projects within multi-project build that uses the plugin itself

I'm developing a library that includes an sbt plugin. Naturally, I'm using sbt to build this (multi-project) library. My (simplified) project looks as follows:
myProject/ # Top level of library
-> models # One project in the multi-project sbt build.
-> src/main/scala/... # Defines common models for both sbt-plugin and framework
-> sbt-plugin # The sbt plugin build
-> src/main/scala/...
-> framework # The framework. Ideally, the sbt plugin is run as part of
-> src/main/scala/... # compiling this directory.
-> project/ # Multi-project build configuration
Is there a way to have the sbt-plugin defined in myProject/sbt-plugin be hooked into the build for myProject/framework all in a unified build?
Note: similar (but simpler) question: How to develop sbt plugin in multi-project build with projects that use it?
Is there a way to have the sbt-plugin defined in myProject/sbt-plugin be hooked into the build for myProject/framework all in a unified build?
I have a working example on Github eed3si9n/plugin-bootstrap. It's not super pretty, but it kind of works. We can take advantage of the fact that sbt is recursive.
The project directory is another build inside your build, which knows how to build your build. To distinguish the builds, we sometimes use the term proper build to refer to your build, and meta-build to refer to the build in project. The projects inside the metabuild can do anything any other project can do. Your build definition is an sbt project.
By extension, we can think of the sbt plugins to be library- or inter-project dependencies to the root project of your metabuild.
meta build definition (project/plugins.sbt)
In this example, think of the metabuild as a parallel universe or shadow world that has parallel multi-build structure as the proper build (root, model, sbt-plugin).
To reuse the source code from model and sbt-plugin subprojects in the proper build, we can re-create the multi-project build in the metabuild. This way we don't need to get into the circular dependency.
addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5")
lazy val metaroot = (project in file(".")).
dependsOn(metaSbtSomething)
lazy val metaModel = (project in file("model")).
settings(
sbtPlugin := true,
scalaVersion := "2.10.6",
unmanagedSourceDirectories in Compile :=
mirrorScalaSource((baseDirectory in ThisBuild).value.getParentFile / "model")
)
lazy val metaSbtSomething = (project in file("sbt-plugin")).
dependsOn(metaModel).
settings(
sbtPlugin := true,
scalaVersion := "2.10.6",
unmanagedSourceDirectories in Compile :=
mirrorScalaSource((baseDirectory in ThisBuild).value.getParentFile / "sbt-plugin")
)
def mirrorScalaSource(baseDirectory: File): Seq[File] = {
val scalaSourceDir = baseDirectory / "src" / "main" / "scala"
if (scalaSourceDir.exists) scalaSourceDir :: Nil
else sys.error(s"Missing source directory: $scalaSourceDir")
}
When sbt loads up, it will build metaModel and metaSbtSomething first, and use metaSbtSomething as a plugin to your proper build.
If you have any other plugins you need you can just add it to project/plugins.sbt normally as I've added sbt-doge.
proper build (build.sbt)
The proper build looks like a normal multi-project build.
As you can see framework subproject uses SomethingPlugin. Important thing is that they share the source code, but the target directory is completely separated, so there are no interference once the proper build is loaded, and you are changing code around.
import Dependencies._
lazy val root = (project in file(".")).
aggregate(model, framework, sbtSomething).
settings(inThisBuild(List(
scalaVersion := scala210,
organization := "com.example"
)),
name := "Something Root"
)
// Defines common models for both sbt-plugin and framework
lazy val model = (project in file("model")).
settings(
name := "Something Model",
crossScalaVersions := Seq(scala211, scala210)
)
// The framework. Ideally, the sbt plugin is run as part of building this.
lazy val framework = (project in file("framework")).
enablePlugins(SomethingPlugin).
dependsOn(model).
settings(
name := "Something Framework",
crossScalaVersions := Seq(scala211, scala210),
// using sbt-something
somethingX := "a"
)
lazy val sbtSomething = (project in file("sbt-plugin")).
dependsOn(model).
settings(
sbtPlugin := true,
name := "sbt-something",
crossScalaVersions := Seq(scala210)
)
demo
In the SomethingPlugin example, I'm defining something task that uses foo.Model.x.
package foo
import sbt._
object SomethingPlugin extends AutoPlugin {
def requries = sbt.plugins.JvmPlugin
object autoImport {
lazy val something = taskKey[Unit]("")
lazy val somethingX = settingKey[String]("")
}
import autoImport._
override def projectSettings = Seq(
something := { println(s"something! ${Model.x}") }
)
}
Here's how we can invoke something task from the build:
Something Root> framework/something
something! 1
[success] Total time: 0 s, completed May 29, 2016 3:01:07 PM
1 comes from foo.Model.x, so this demonstrates that we are using the sbt-something plugin in framework subproject, and that the plugin is using metaModel.

SBT: How to set common scala version for multiproject

I have a multi-SBT-project in IntellJ Idea. My SBT file in the root dir looks like this:
name := "PlayRoot"
version := "1.0"
lazy val shapeless_learn = project.in(file("shapeless_learn")).dependsOn(common)
lazy val scalaz_learn = project.in(file("scalaz_learn")).dependsOn(common)
lazy val common = project.in(file("common"))
lazy val root = project.in(file(".")).aggregate(common, shapeless_learn, scalaz_learn)
scalaVersion := "2.11.7"
Then I have folders for each of the projects: ./common, ./shapeless_learn, ./scalaz_learn and each has its own build.sbt there. But for some reason I require to put in each of the subproject build.sbt files the line scalaVersion := "2.11.7".
If I forget to do that, the build fails with the message:
Error:Unresolved dependencies: common#common_2.10;0.1-SNAPSHOT: not found
See complete log in ...
For some reason if I do not specify that my scala version is 2.11.7, sbt falls back to 2.10 and tries to find common project that is built for 2.10 which I do not have.
I always keep forgetting adding scalaVersion := "2.11.7" to the newly created project and it keeps bugging me. I also would prefer sbt generating build.sbt with some default data, but instead it requires me not to forget to create it manually.
Is there any way I could set the single scala version for all projects and sub-projects in a single place? I figured that I could add a separate lazy val commonSettings = Seq { scalaVersion := "2.11.7" } in a root definition. And for each lazy val project definition I should add in the end .settings(commonSettings). This is nice, but still doesn't look beautiful enough - I should do this for every project definition. Is there a better way?
Is there any way I could create a template for a newly created project, so when I just put line lazy val newProject = ..., it will put an appropriate build.sbt file there with the contents I want?
Use
scalaVersion in ThisBuild := "2.11.7"
in the root build.sbt.

hot swap in sbt project without play-plugin

When I am using play framework, every time I've changed the code, it will take effect automatically by re-compile the code.
However, when I'm using sbt to run a project without play-plugin, it won't take effect.
I'm wondering if there were a way to make sbt project hot swap the changed code.
My build.sbt is as below:
version in ThisBuild := "1.0-SNAPSHOT"
scalaVersion in ThisBuild := "2.11.6"
lazy val `frontend` = (project in file("frontend")).
enablePlugins(PlayScala).
enablePlugins(DockerPlugin).
settings(
name := "frontend",
libraryDependencies ++= Dependencies.frontend
).dependsOn(`api`).aggregate(`api`)
lazy val `backend` = (project in file("backend")).
enablePlugins(JavaAppPackaging).
enablePlugins(DockerPlugin).
settings(
name := "backend",
libraryDependencies ++= Dependencies.backend ++ Seq(cache, ws)
).dependsOn(`api`).aggregate(`api`)
lazy val `api` = (project in file("api")).
settings(
name := "api",
libraryDependencies += ws
)
And what I have configured in intellij idea is like below as a sbt task(I can't post images by now):
"project backend" ~run
However, every time I've changed the code in backend, It won't take effect after I've call backend from the frontend.
I'm wondering how I can solve the problem. Thanks for your guys' help.
You can have sbt automatically recompiling any changes by invoking it like this:
sbt ~compile
If you use ~run, on every change the changeed classes will be compiled and project rerun again.
If it does not work, you might explain more about your project and structure.
Open two SBT window.
The one run ~compile, and another run ~run.
Hope it will be help.