Conditional libraries in build.sbt - sbt

Using buildsbt. I'm trying to do something like this:
if (condition) {
libraryDependencies += ... // library from maven
}
else {
unmanagedJars in Compile += ... // local library instead
}
However, build.sbt doesn't like this at all. I've been able to accomplish this using side effects, but that's obviously undesirable. Any advice would be appreciated. Thanks.

You can do the following:
val additionalLibraryDependencies = Seq(...)
val additionalUnmanagedJars = Seq(...)
libraryDependencies ++=(
if (condition) {
additionalLibraryDependencies
}
)
unmanagedJars in Compile ++= (
if (!condition) {
additionalUnmanagedJars
}
)
To set the condition from command line you should add the following lines:
val someValueFromCommandLine = System.getProperty("key.of.the.value", "false")
if (someValueFromCommandLine.equals("true")){
...
}
You can pass it like sbt -Dkey.of.the.value=true

This might be easier to do using a Build.scala build definition.
Here is an example build.sbt file (this is optional):
import sbt._
import sbt.Keys._
libraryDependencies ++= Seq(
"org.postgresql" % "postgresql" % "9.3-1101-jdbc41",
"redis.clients" % "jedis" % "2.5.2"
)
Then create another file your_project_home/project/Build.scala
import sbt._
import Keys._
object BuildSettings {
val condition = false
val buildSettings = Defaults.defaultSettings ++ Seq(
version := "1.0.0",
if(condition) libraryDependencies ++= Seq("commons-codec" % "commons-codec" % "1.9")
else unmanagedJars in Compile += file("/tmp/nil")
)
}
object MyBuild extends Build {
import BuildSettings._
lazy val root: Project = Project("root", file("."), settings = buildSettings)
}
Your project structure should look like this:
.
├── build.sbt
├── project
│   └── Build.scala
└── target
You can make the "condition" to be whatever what you need (here I just set it to false). The libraryDependencies defined inside build.sbt will always be included. The ones defined in Build.scala will depend on the "condition."
Verify that everything works as expected from the command line using:
sbt "inspect libraryDependencies"

Related

Additional resource generator with sbt native packager

I have a submodule, that is compiled by invoking external command. I would like to include generated file into jar. So I wrote a task:
```
myTask := {
import sys.process.stringSeqToProcess
Seq("my", "command") !
}
unmanagedResourceDirectories in Compile += baseDirectory.value / "dist"
cleanFiles <+= baseDirectory { base => base / "dist" }
Keys.`package` <<= (Keys.`package` in Compile) dependsOn npmBuildTask.toTask
and when I invoke mySubmodule/package task it works well. But when I invoke stage task from sbt-native-packager my task is ignored(is not executed).
There are a couple of options to solve this issue. I assume you want to add the dist folder to your resulting application jar.
Your configuration doesn't work because stage doesn't depend on package. This results npmBuildTask not being called.
1. Add dependency to stage
The easiest way to fix this is by simply adding the npmBuildTask as a dependency to stage
stage <<= stage dependsOn npmBuildTask.toTask
I wouldn't recommend this approach.
2. Resource generators
SBTs Resoure Generators are exactly defined for this purpose. An inline version could look like this
resourceGenerators in Compile += Def.task {
streams.value.log.info("running npm generator")
val base = (resourceManaged in Compile).value / "dist"
// A resource generator returns a Seq[File]. This is just an example
List("index.js", "test.js").map { file =>
IO.writeLines(base / file, List("var x = 1"))
base / file
}
}.taskValue
Or you could extract this in an AutoPlugin to separate the "what" and "how.
3. AutoPlugin and resource generators
Create project/NpmPlugin.scala and add the following content
import sbt._
import sbt.Keys._
import sbt.plugins.JvmPlugin
object NpmPlugin extends AutoPlugin {
override val requires = JvmPlugin
override val trigger = AllRequirements
object autoImport {
val npmBuildTask = TaskKey[Seq[File]]("npm-build-task", "Runs npm and builds the application")
}
import autoImport._
override def projectSettings: Seq[Setting[_]] = Seq(
// define a custom target directory for npm
target in npmBuildTask := target.value / "npm",
// the actual build task
npmBuildTask := {
val npmSource = (target in npmBuildTask).value
val npmTarget = (resourceManaged in Compile).value / "dist"
// run npm here, which generates the necessary values
streams.value.log.info("running npm generator")
// move generated sources to target folder
IO.copyDirectory(npmSource, npmTarget)
// recursively get all files in the npmTarget
(npmTarget ***).get
},
resourceGenerators in Compile += npmBuildTask.taskValue
)
}
The build.sbt will then look like this
name := "resource-gen-test"
version := "1.0"
enablePlugins(JavaAppPackaging)
Pretty clean :)
4. Use mappings
Last but not least you could use mappings. They are the low level detail that drives a lot of the package-generation in sbt. The main idea of this solution is to
Create a task that returns a mapping definition ( Seq[(File, String)] )
Append this to the appropriate mappings
The advantage of this approach is that you are more flexible where you want to put your mappings.
import sbt._
import sbt.Keys._
import sbt.plugins.JvmPlugin
import com.typesafe.sbt.SbtNativePackager.Universal
import com.typesafe.sbt.SbtNativePackager.autoImport.NativePackagerHelper._
object NpmMappingsPlugin extends AutoPlugin {
override val requires = JvmPlugin
override val trigger = AllRequirements
object autoImport {
val npmBuildTask = TaskKey[Seq[(File, String)]]("npm-build-task", "Runs npm and builds the application")
}
import autoImport._
override def projectSettings: Seq[Setting[_]] = Seq(
// define a custom target directory for npm
target in npmBuildTask := target.value / "npm" / "dist",
// the actual build task
npmBuildTask := {
val npmTarget = (target in npmBuildTask).value
// run npm here, which generates the necessary values
streams.value.log.info("running npm generator")
// recursively get all files in the npmTarget
// contentOf(npmTarget) would skip the top-level-directory
directory(npmTarget)
},
// add npm resources to the generated jar
mappings in (Compile, packageBin) ++= npmBuildTask.value,
// add npm resources to resulting package
mappings in Universal ++= npmBuildTask.value
)
}
As you can see in this approach we can easily add the resulting files to different mappings.
However I only recommend this approach if you need this kind of flexibility as it requires a bit more knowledge of native-packager.

How to obtain the value of scalacOptions for the meta-project in a plugin?

In the ensime-sbt plugin, we need to be able to obtain the compiler flags that the sbt process is using to compile the build definition (i.e. everything under project).
We have the State object, but I can't see any way to get the compiler flags, where are they?
Note: this is not the compile flags for the projects themselves, I mean only for the build definition.
e.g. say the project has this in the project/plugins.sbt
scalacOptions += "-Xfuture"
how can we read that from the plugin?
This is somewhat related to How to share version values between project/plugins.sbt and project/Build.scala?
You can generate a build file for the project. And, to do that, you have to add the plugin in both the meta-project and the meta-project for the meta-project.
import sbt._
import Keys._
object MyPlugin extends AutoPlugin {
object autoImport {
val scalacOptions4Meta = SettingKey[Seq[String]]("scalacOptions4Meta")
val mygenerator = TaskKey[Seq[File]]("mygenerator")
}
import autoImport._
override def trigger = allRequirements
override lazy val projectSettings = Seq(
mygenerator := {
val file = sourceManaged.value / "settings4Meta.scala"
val opts = (scalacOptions in Compile).value
.map(opt => "\"" + opt + "\"")
val content = s"""
import sbt._
import Keys._
object MyBuild extends Build {
lazy val root = Project("root", file("."))
.settings(
MyPlugin.autoImport.scalacOptions4Meta := Seq(${opts.mkString(",")})
)
}"""
IO.write(file, content)
Seq(file)
}
)
}
project/plugins.sbt:
addSbtPlugin("myplugin" % "myplugin" % "0.1-SNAPSHOT")
scalacOptions := Seq("-Xfuture")
sourceGenerators in Compile += mygenerator.taskValue
project/project/plugins.sbt:
addSbtPlugin("myplugin" % "myplugin" % "0.1-SNAPSHOT")

How to import `%%%` in `Build.scala` in a scalajs project?

We can easily use %%% in a build.sbt in a scalajs project, like:
libraryDependencies +=
"com.lihaoyi" %%% "utest" % "0.3.0" % "test"
(live demo: https://github.com/scala-js/scala-js-pickling/blob/master/build.sbt#L92)
But when I try to use project/Build.scala to write the same build file, it can't compile, and I've no idea how to import the %%%:
import org.scalajs.sbtplugin.cross.{CrossType, CrossProject}
import sbt._
import Keys._
object Build extends sbt.Build {
lazy val crossProject = CrossProject("server", "client", file("."), CrossType.Full)
.settings(
/* Shared settings */
libraryDependencies ++= Seq(
"io.github.widok" %%% "widok" % "0.2.1", // !!! can't compile
"com.lihaoyi" %%% "upickle" % "0.2.6")
)
.jsSettings(
/* Scala.js settings */
)
.jvmSettings(
/* JVM settings */
)
lazy val js = crossProject.js
lazy val jvm = crossProject.jvm
}
The lines with %%% are not compilable, how to fix them?
You need to add following import
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
You need to add the following import:
import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._

How to extract dependency jar to specific folder during compilation?

These are my project's dependencies:
libraryDependencies ++= Seq(
javaJdbc,
javaEbean,
cache,
javaWs,
"com.company" % "common_2.11" % "2.3.3"
)
In com.company.common_2.11-2.3.3 there is a jar file common_2.11-2.3.3-adtnl.jar.
How can I during compilation process in build.sbt tell SBT to extract its contents to specific folder in my project?
Use the following in build.sbt:
def unpackjar(jar: File, to: File): File = {
println(s"Processing $jar and saving to $to")
IO.unzip(jar, to)
jar
}
resourceGenerators in Compile += Def.task {
val jar = (update in Compile).value
.select(configurationFilter("compile"))
.filter(_.name.contains("common"))
.head
val to = (target in Compile).value / "unjar"
unpackjar(jar, to)
Seq.empty[File]
}.taskValue
It assumes that "common" is a unique part amongst all of your dependencies. You'd need to fix filter otherwise.
It also assumes that you don't really want the files at compile, but a bit later when package is called. You'd need to move the code between Def.task{...} to compile in Compile block like:
compile in Compile <<= (compile in Compile).dependsOn(Def.task {
val jar = (update in Compile).value
.select(configurationFilter("compile"))
.filter(_.name.contains("common"))
.head
val to = (target in Compile).value / "unjar"
unpackjar(jar, to)
Seq.empty[File]
})

How configure aspectj compilation in playframework 2.1.1

I have added to plugins.sbt this declaration
addSbtPlugin("com.typesafe.sbt" % "sbt-aspectj" % "0.9.0")
Now I would like to configure this plugin to compile my java controller classes using aspect library org.springframework:spring-aspects:3.1.4 as with aspectj-maven-plugin
I have set this configuration :
import sbt._
import Keys._
import play.Project._
import com.typesafe.sbt.SbtAspectj._
import com.typesafe.sbt.SbtAspectj.AspectjKeys._
object ApplicationBuild extends Build {
val appDependencies = Seq(javaCore)
val main = play.Project(appName, appVersion, appDependencies).settings(
AspectjKeys.verbose in Aspectj := true,
AspectjKeys.showWeaveInfo in Aspectj := true,
AspectjKeys.inputs in Aspectj <+= compiledClasses
)
}
But it does fail.
[error] Reference to undefined setting:
[error]
[error] aspectj:inputs from aspectj:inputs
I am really a newbie with the sbt thing.
The plugin github page : https://github.com/sbt/sbt-aspectj
Ok, I make it works, thanks to sbt mailing list, cf. https://groups.google.com/forum/?fromgroups=#!topic/simple-build-tool/MUXyfKigC7w
and the playframework mailing list also, cf. https://groups.google.com/forum/?fromgroups=#!topic/play-framework/RfJFEwVbUUk
It was not very hard in fact but something you can't see things.
import sbt._
import Keys._
import play.Project._
import com.typesafe.sbt.SbtAspectj._
import com.typesafe.sbt.SbtAspectj.AspectjKeys._
object ApplicationBuild extends Build {
val appDependencies = Seq(javaCore, filters)
val main = play.Project(appName, appVersion, appDependencies)
.settings(aspectjSettings: _*)
.settings(
libraryDependencies += "org.springframework" % "spring-aspects" % "3.1.4.RELEASE",
libraryDependencies += "org.springframework.security" % "spring-security-aspects" % "3.1.4.RELEASE",
sourceLevel := "-1.7",
verbose in Aspectj := false,
showWeaveInfo in Aspectj := false,
inputs in Aspectj <+= compiledClasses,
binaries in Aspectj <++= update map { report =>
report.matching(
moduleFilter(organization = "org.springframework", name = "spring-aspects")
|| moduleFilter(organization = "org.springframework.security", name = "spring-security-aspects")
)
},
products in Compile <<= products in Aspectj,
products in Runtime <<= products in Compile
)
}
Don't forget to add this in in plugins.sbt, with a new line separator between declaration
addSbtPlugin("com.typesafe.sbt" % "sbt-aspectj" % "0.9.0")

Resources