Is there a way in sbt to only aggregate specific tasks? - sbt

My build consists of a root project, sub projects, and other meta projects. The sub projects produce a JAR while the meta projects are for other things like packaging etc.
See Cross platform build with SBT for the reason why I am doing this. For now assume this the right thing to do, but if not then feel free to comment on that question.
What I want to be able to do is to aggregate all my sub projects like so:
lazy val root = (project in file(".")).
aggregate(subProjects: _*)
val subProjects = Seq(projectA, projectB)
lazy val projectA = project
lazy val projectB = project
For the packaging I have something like:
lazy val packaging = (project in file("packaging").
enablePlugins(JavaServerAppPackaging). // Using sbt-native-packager
settings(
libraryDependencies ++= Seq(projectA.projectID, projectB.projectID)
)
Now before packaging I have to publish the sub projects:
sbt> publishLocal
And then to package from those published artifacts I do:
sbt> packaging/universal:packageBin
This all works great but now for the real question, how can I aggregate specific tasks? For example I want to be able to clean all projects including the packaging one.
I know that it is possible to do the opposite, see Disable aggregate for sbt custom task.
I've had a bit of a look at sbt.Aggregation but I can't make head or tail of it.
My ideal solution would look something like:
lazy val root = (project in file(".")).
aggregate(subProjects: _*).
settings(aggregatedTasks(metaTasks, metaProjects ): _*)
...
val metaProjects = Seq(packaging, someOtherProject)
val metaTasks = Seq(clean, someOtherTask)
def aggregatedTasks(tasks: Seq[TaskKey[_]], projects: Seq[Project]): Seq[Setting[_]] = {
??? // Is there a way to do this?
}

Related

How to set up a multi-project SBT project with inheritance and shared dependencies

I'd like to create an SBT project with inheritance and shared dependencies.
With Maven's POM files, there is the idea of Project Inheritance where you can set a parent project. I'd like to do the same thing with SBT.
The xchange-stream library uses Maven's Project Inheritance to resolve subproject dependencies when compiled from the parent project.
Here is my idea of what the file structure would look like:
sbt-project/
project/
dependencies.scala # Contains dependencies common to all projects
build.sbt # Contains definition of parent project with references
# to subprojects
subproject1/
build.sbt # Contains `subproject3` as a dependency
subproject2/
build.sbt # Contains `subproject3` as a dependency
subproject3/
build.sbt # Is a dependency for `subproject1` and `subproject2`
Where project1 and project2 can include project3 in their dependencies lists like this:
libraryDependencies ++= "tld.organization" % "project3" % "1.0.0"
Such that when subproject1 or subproject2 are compiled by invoking sbt compile from within their subdirectories, or when the parent: sbt-project is compiled from the main sbt-project directory, then subproject3 will be compiled and published locally with SBT, or otherwise be made available to the projects that need it.
Also, how would shared dependencies be specified in sbt-project/build.sbt or anywhere in the sbt-project/project directory, such that they are useable within subproject1 and subproject2, when invoking sbt compile within those subdirectories?
The following examples don't help answer either of the above points:
jbruggem/sbt-multiproject-example:
Uses recursive build.sbt files, but doesn't share dependencies among child projects.
Defining Multi-project Builds with sbt: pbassiner/sbt-multi-project-example:
Uses a single build.sbt file for the projects in their subdirectories.
sachabarber/SBT_MultiProject_Demo:
Uses a single build.sbt file.
Such that when subproject1 or subproject2 are compiled by invoking sbt compile from within their subdirectories...
Maybe Maven is meant to be used together with the shell environment and cd command, but that's not how sbt works at least as of sbt 1.x in 2019.
The sbt way is to use sbt as an interactive shell, and start it at the top level. You can then either invoke compilation as subproject1/compile, or switch into it using project subproject1, and call compile in there.
house plugin
A feature similar to parent POM would be achieved by creating a custom plugin.
package com.example
import sbt._
import Keys._
object FooProjectPlugin extends AutoPlugin {
override def requires = plugins.JvmPlugin
val commonsIo = "commons-io" % "commons-io" % "2.6"
override def buildSettings: Seq[Def.Setting[_]] = Seq(
organization := "com.example"
)
override def projectSettings: Seq[Def.Setting[_]] = Seq(
libraryDependencies += commonsIo
)
}
sbt-sriracha
It's not exactly what you are asking for, but I have an experimental plugin that allows you to switch between source dependency and binary dependency. See hot source dependencies using sbt-sriracha.
Using that you could create three individual sbt builds for project1, project2, and project3, all located inside $HOME/workspace directory.
ThisBuild / scalaVersion := "2.12.8"
ThisBuild / version := "0.1.1-SNAPSHOT"
lazy val project3Ref = ProjectRef(workspaceDirectory / "project3", "project3")
lazy val project3Lib = "tld.organization" %% "project3" % "0.1.0"
lazy val project1 = (project in file("."))
.enablePlugins(FooProjectPlugin)
.sourceDependency(project3Ref, project3Lib)
.settings(
name := "project1"
)
With this setup, you can launch sbt -Dsbt.sourcemode=true and it will pick up project3 as a subproject.
You can use Mecha super-repo concept. Take a look on the setup and docs here: https://github.com/storm-enroute/mecha
The basic idea is that you can combine dependent sbt projects (with their own build.sbt) under single root super-repo sbt project:
/root
/project/plugins.sbt
repos.conf
/project1
/src/..
/project/plugins.sbt
build.sbt
/project2
/src/..
/project/plugins.sbt
build.sbt
Please, note that there is no build.sbt in the root folder!
Instead there is repos.conf file. It contains definition of the sub-repos and looks like the folowing:
root {
dir = "."
origin = ""
mirrors = []
}
project1 {
dir = "project1"
origin = "git#github.com:some_user/project1.git"
mirrors = []
}
project2 {
dir = "project2"
origin = "git#github.com:some_user/project2.git"
mirrors = []
}
Then you can specify the Inter-Project, source-level Dependencies within individual projects.
There are two approaches:
dependencies.conf file
or in the build source code
For more details, please, see the docs

How to publish an artifact with pom-packaging in SBT?

I have a multi-project build in SBT where some projects should aggregate dependencies and contain no code. So then clients could depend on these projects as a single dependency instead of directly depending on all of their aggregated dependencies. With Maven, this is a common pattern, e.g. when using Spring Boot.
In SBT, I figured I can suppress the generation of the empty artifacts by adding this setting to these projects:
packagedArtifacts := Classpaths.packaged(Seq(makePom)).value
However, the makePom task writes <packaging>jar</packaging> in the generated POM. But now that there is no JAR anymore, this should read <packaging>pom</packaging> instead.
How can I do this?
This question is a bit old, but I just came across the same issue and found a solution. The original answer does point to the right page where this info can be found, but here is an example. It uses the pomPostProcess setting to transform the generated POM right before it is written to disk. Essentially, we loop over all the XML nodes, looking for the element we care about and then rewrite it.
import scala.xml.{Node => XmlNode, NodeSeq => XmlNodeSeq, _}
import scala.xml.transform._
pomPostProcess := { node: XmlNode =>
val rule = new RewriteRule {
override def transform(n: XmlNode): XmlNodeSeq = n match {
case e: Elem if e != null && e.label == "packaging" =>
<packaging>pom</packaging>
case _ => n
}
}
new RuleTransformer(rule).transform(node).head
},
Maybe you could modify the result pom as described here: Modifying the generated POM
You can disable publishing the default artifacts of JAR, sources, and docs, then opt in explicitly to publishing the POM. sbt produces and publishes a POM only, with <packaging>pom</packaging>.
// This project has no sources, I want <packaging>pom</pom> with dependencies
lazy val bundle = project
.dependsOn(moduleA, moduleB)
.settings(
publishArtifact := false, // Disable jar, sources, docs
publishArtifact in makePom := true,
)
lazy val moduleA = project
lazy val moduleB = project
lazy val moduleC = project
Run sbt bundle/publishM2 to verify the POM in ~/.m2/repository.
I dare say this is almost intuitive, a rare moment of pleasant surprise with sbt 😅
I confirmed this with current sbt 1.3.9, and 1.0.1, the oldest launcher I happen to have installed on my machine.
The Artifacts page in the reference docs may be helpful, perhaps this trick should be added there.

What does project location mean in SBT?

To define a project, I do
project in file(".")
What is the function of file("."), as opposed to settings baseDirectory?
For example, what is the difference between
lazy val myProject = project in file("foo")
and
lazy val myProject = (project in file(".")).settings(
baseDirectory := file("foo"))
For projects, there is no difference between using project in file(...) and setting baseDirectory yourself. use show baseDirectory to convince yourself :)
However, since baseDirectory is a setting, it can be used with any scope, e.g. you can set the base directory for a specific configuration or task.

using sbt for multi project build

I am totally new to SBT. Suppose I have three Scala projects: project_a, project_b, project_c. How should I go about building all three projects into one jar file? Suppose I use project_a as the root project. The directory structure is like
--project_a
--build.sbt
--project_b
--project_c
Following the instructions on sbt webiste, I created a build.sbt file, which looks something like
lazy val root = (project.in(file("."))).aggregate(project_b, project_c)
lazy val project_b = project
lazy val project_c = project
I put the build.sbt under the project_a. When I run sbt clean compile under project_a, a new (kinda of empty) project_b and project_c folders are created under the folder project_a. However, in the build.sbt file, I meant project_b and project_c to refer to the original folders I already created which contains the source and test code, and which are outside project_a.
Can someone let me know what I did wrong?
Thanks
First, your multi-project setup is not right.
Getting Started guide says:
Aggregation means that running a task on the aggregate project will also run it on the aggregated projects.
If you have project_a that uses project_b and project_c, then you need root in addition to project_a, project_b, and project_c.
Root can aggregate all three (a, b, and c), but it only aggregates commands given to sbt shell, for instance for compiling all three at the same time.
project_a should be set up to depend on project_b and project_c.
Here's an example:
lazy val commonSettings = Seq(
scalaVersion := "2.11.4",
organization := "com.example"
)
lazy val root = (project in file(".")).
aggregate(project_a, project_b, project_c).
settings(commonSettings: _*)
lazy val project_a = project.
dependsOn(project_b, project_c).
settings(commonSettings: _*).
settings(
// your settings here
)
lazy val project_b = project.
settings(commonSettings: _*)
lazy val project_c = project.
settings(commonSettings: _*)
How should I go about building all three projects into one jar file?
If you just want *.class files from your own projects, you can see an example on Macro Projects.
If you want *.class files and library dependencies, you need sbt-assembly.

Inter-module dependencies dependent on config possible in SBT?

I'm attempting to scope a dependency to a module in the same project using SBT's configurations.
In production, this dependency is satisfied by a jar on the classpath, but during dev it would be nice to do server/config-a:run or server/config-b:run to select the dependency manually.
Currently, I have something like this:
lazy val configA = config("config-a") extend Runtime
lazy val configB = config("config-b") extend Runtime
lazy val DevConfigA = Project(id = "dev-config-a", base = file("dev-config-a"))
lazy val DevConfigB = Project(id = "dev-config-b", base = file("dev-config-b"))
lazy val server = Project(id = "server",
base = file("server"),
dependencies = Seq(common))
.configs(configA, configB)
.dependsOn(DevConfigA % configA, DevConfigB % configB)
DevConfigA and DevConfigB bring in resources used for configuration. We want exactly one of them to be loaded. The goal is that server/config-a:run would depend on DevConfigA module, and not DevConfigB.
I had to move the configs and dependsOn out of the call to Project.apply to get it to compile. After that, the DevConfig* dependencies aren't showing up when I server/config-a:run or if I call show server/config-a:dependency-classpath.
Is there a way to make inter-module dependencies dependent on the config?
Yes, there's a way to make dependencies configuration-dependent - use libraryDependencies config-scoped.
I'm using the latest stable release of SBT.
[server]> show sbtVersion
[info] 0.13.1
Let's assume you need different versions of a library, e.g. scalaz, based upon what configuration you execute run with. As a matter of fact, you don't have to worry about the task, but the dependencies available in a given configuration, and since libraryDependencies drives it, I'm going to use it.
[server]> help libraryDependencies
Declares managed dependencies.
Here's the build.sbt that gives what you want.
build.sbt
lazy val configA = config("config-a") extend Runtime
lazy val configB = config("config-b") extend Runtime
lazy val server = project in file(".") configs(configA, configB)
val scalaz705 = "org.scalaz" %% "scalaz-core" % "7.0.5"
val scalaz710_M5 = "org.scalaz" %% "scalaz-core" % "7.1.0-M5"
libraryDependencies in configA += scalaz705
libraryDependencies in configB += scalaz710_M5
With the above build.sbt sbt lets us pick different versions of Scalaz based upon configuration.
[server]> show libraryDependencies
[info] List(org.scala-lang:scala-library:2.10.3)
[server]> show config-a:libraryDependencies
[info] List(org.scala-lang:scala-library:2.10.3, org.scalaz:scalaz-core:7.0.5)
[server]> show config-b:libraryDependencies
[info] List(org.scala-lang:scala-library:2.10.3, org.scalaz:scalaz-core:7.1.0-M5)

Resources