Use external libraries inside the build.sbt file - scala

Is it somehow possible to use an external library inside the build.sbt file?
E.g. I want to write something like this:
import scala.io.Source
import io.circe._ // not possible
version := myTask
lazy val myTask: String = {
val filename = "version.txt"
Source.fromFile(filename).getLines.mkString(", ")
// do some json parsing using the circe library
// ...
}

One of the things I actually like about sbt is that the build project is (in most ways) just another project (which is also potentially configured by a meta-build project configured by a meta-meta-build project, etc.). This means you can just drop the following line into a project/build.sbt file:
libraryDependencies += "io.circe" %% "circe-jawn" % "0.11.1"
You could also add this to plugins.sbt if you wanted, or any other .sbt file in the projects directory, since the filenames (excluding the extension) have no meaning beyond human convention, but I'd suggest following convention and going with build.sbt.
Note though that sbt implicitly imports sbt.io in .sbt files, so the circe import in your build.sbt (at the root level—i.e. the build config, not the build build config) will need to look like this:
import _root_.io.circe.jawn.decode
scalaVersion := decode[String]("\"2.12.8\"").right.get
(For anyone who hasn't seen it before, the _root_ here just means "start the package hierarchy here instead of assuming io is the imported one".)

Related

How to output slf4j log messages from sbt?

I'm currently writing an sbt plugin. The tasks that I define are using functionality from libraries that are otherwise unrelated to sbt and that I don't want to change. These libraries use slf4j for logging. I would like the logging output to show up in the sbt console as if I had used streams.value.log, so that I can e. g. turn debug logging on and off in the usual sbt ways like set someTask / logLevel := Level.Debug. Is there a way to do this?
It seems like sbt-slf4j is supposed to solve my problem:
https://index.scala-lang.org/eltimn/sbt-slf4j/sbt-slf4j/1.0.4?target=_2.12
But I wasn't able to get it to work. My project/build.sbt looks like this:
libraryDependencies += "com.eltimn" %% "sbt-slf4j" % "1.0.4"
resolvers += Resolver.bintrayRepo("eltimn", "maven")
and build.sbt like so:
import org.slf4j.LoggerFactory
import org.slf4j.impl.SbtStaticLoggerBinder
val foo = taskKey[Unit]("foobar")
foo := {
SbtStaticLoggerBinder.sbtLogger = streams.value.log
// symbolic implementation, the actual implementation lives in a library
val logger = LoggerFactory.getLogger("foobar")
logger.warn("Hello!")
}
But running foo does not result in a warning being printed. A warning is printed if I change it to LoggerFactory.getLogger(streams.value.log.name), but this is not an option because again, the code lives in a library.
Is there any good way to solve this?

How to access library dependencies at build-time in SBT/Scala build?

For example, suppose I have a file project/CodeGeneration.scala that generates "managed" source-code, and suppose that object (CodeGeneration) needs to leverage a third-party library -- say jsoup...
import org.jsoup._
object CodeGeneration {
def generateCode = /* Generate code using jsoup... */
}
Simply adding a line for jsoup to your libraryDependencies in build.sbt doesn't do the trick; it leads to a compilation error complaining about the missing jsoup object/namespace.
So, (how) can one access this dependency from "meta" code -- code that generates other code?
It seems the solution is to leverage sbt's "recursive" nature, and put an additional build.sbt file in the project directory. So, for example, project/build.sbt might look like this:
libraryDependencies += "org.jsoup" % "jsoup" % "1.11.2"
There's more detail in sbt's official documentation.

Cannot get sbt-concat to bundle styles from sbt-sass or sbt-less

(Example project provided) I cannot get sbt-concat to work as designed to find and concatenate stylesheets that result from styles that may be produced from preprocessor tasks. In my production app, I'm trying to use it to bundle select minified output files from sbt-sass. It does not work within the complex setup of that project, so I created an example project to see if I could get it to work at all. It does not work in the example project either. Here is a test project build.sbt that tries to create several bundles, with just about every possibility I can think of, just to see if any of them work (public Github repo, which you should be able to clone and immediately replicate the problem):
import com.typesafe.sbt.web.Import.WebKeys._
import com.typesafe.sbt.web.pipeline.Pipeline
name := """sbt-concat-test"""
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayScala, SbtWeb)
scalaVersion := "2.11.1"
libraryDependencies ++= Seq(
jdbc,
anorm,
cache,
ws
)
resolvers += Resolver.sonatypeRepo("releases")
includeFilter in (Assets, LessKeys.less) := "*.less"
excludeFilter in (Assets, LessKeys.less) := "_*.less"
val myPipelineTask = taskKey[Pipeline.Stage]("Some pipeline task")
myPipelineTask := { mappings => println(mappings); mappings }
pipelineStages := Seq(myPipelineTask, concat)
Concat.groups := Seq(
"style-group1.css" -> group(sourceDirectory.value ** "*.css"),
"style-group2.css" -> group(baseDirectory.value ** "*.css"),
"style-group3.css" -> group((sourceDirectory in Assets).value ** "*.css"),
"style-group4.css" -> group(target.value ** "*.css"),
"style-group5.css" -> group(Seq("core.css", "styles/core.css", "assets/styles/core.css", "app/assets/styles/core.css")),
"style-group6.css" -> group(Seq("lessStyle.css", "ui/lessStyle.css", "styles/ui/lessStyle.css", "assets/styles/ui/lessStyle.css", "app/assets/styles/ui/lessStyle.css")),
"style-group7.css" -> group(Seq("sassStyle.css", "ui/sassStyle.css", "styles/ui/sassStyle.css", "assets/styles/ui/sassStyle.css", "app/assets/styles/ui/sassStyle.css")),
"style-group8.css" -> group(Seq("**/*.css"))
)
I run ; clean; reload; stage from activator to test. I see asset source files copied over into the target folder, with the following results for the declared bundles:
style-group1.css does not exist
style-group2.css contains the contents of button.css and core.css
style-group3.css contains the contents of core.css and button.css
style-group4.css does not exist
style-group5.css contains only the contents of core.css
style-group6.css contains only the contents of compiled lessStyle.scss
style-group7.css contains only the contents of compiled sassStyle.scss
style-group8.css does not exist
I do not understand why the 2nd and 3rd cases do not pick up the preprocessor-produced css files, yet the tailor-made 6th and 7th cases do. Perhaps notably, the result of myPipelineTask shows PathMappings for all source files, as well as the derived css and sourcemaps from the Sass and Less tasks.
According to Typesafe support, the source of my woes is the fact that sbt-concat implements its PathFinder logic in such a way as to only pick up assets that are verbatim the same filename as in the source directory. The sequence of relative filenames works for files in the target directory, but has no pattern matching. This is rather unfortunate.
What does work is to construct a Seq of output files that will exist post-compilation by using a PathFinder on the source directory. So for .scss files, something like:
Concat.groups := {
// Determine the output names of the style files to bundle
// This is really roundabout because sbt-concat only offers 2 ways of
// specifying files, relative paths and using a PathFinder, and the
// latter approach restricts itself to source files instead of output files.
val sourceDir = (sourceDirectory in Assets).value
val scssFiles = (sourceDir ** "*.scss").getPaths
val scssRelativePaths = scssFiles.map(_.stripPrefix(sourceDir.getPath).stripPrefix("/"))
val outputRelativePaths = scssRelativePaths.map(_.stripSuffix(".scss") + ".min.css")
Seq("bundle.min.css" -> group(outputRelativePaths))
}
As a side note, another quirk of sbt-concat is that it doesn't put its new files in its own directory in web-assets:assetsTarget to separate them from artifacts of other pipeline stages. The Concat.parentDir is also unnecessary because you can simply anything you would place in that variable as a prefix for your bundle file name directly.

How can I accessing the scala play plugin from Build.scala

I am not a very sophisticated SBT user, although I have been using it casually for several years. I have made multiple project builds before, but (as always) this particular 'split one project into sub projects' has hit a snag.
The problem
I have a Build.sbt file in the root directory of a project that had the following line in it
lazy val commonPlay = Project(id = "commonPlay", base=file("modules/commonPlay")).
dependsOn(core).enablePlugins(PlayScala)
The important line in it is 'enablePlugins(PlayScala)'. As well as this build.sbt file, I have a plugins.sbt file in the project directory that declared a number of plugins, including "com.typesafe.play" % "sbt-plugin" % "2.2.1"
I am now migrating the project to use a Build.scala file, and in the Build.scala (which is in the project subdirectory) I have the following code
def playModule(dir: String) =
Project(id = dir, base = file(dir), settings = defaultSettings).
enablePlugins(PlayScala)
lazy val core = module("core")
This gives me a 'not found' exception at the PlayScala.
To Date
I've tried messing around with plugins.sbt: adding it under project/project, but decided that I didn't know what I was doing, and I have something of a phobia about not understanding this sort of detail. I had a good read of http://www.scala-sbt.org/0.13.1/docs/Getting-Started/Using-Plugins.html which was very helpful to my understanding, but didn't actually answer the problem.
My expectation is that all I have to do is specify an import, but I'm not sure how to work out what that import would be
Any help would be appreciated
As you can see in this test of the Play framework itself, you need to use play.PlayScala.
Project(id = dir, base = file(dir)).enablePlugins(play.PlayScala)

How can scala-js integrate with sbt-web?

I would like to use scala-js with sbt-web in such a way that it can be compiled to produce javascript assets that are added to the asset pipeline (e.g. gzip, digest). I am aware of lihaoyi's workbench project but I do not believe this affects the asset pipeline. How can these two projects be integrated as an sbt-web plugin?
Scala-js produces js files from Scala files. The sbt-web documentation calls this a Source file task.
The result would look something like this:
val compileWithScalaJs = taskKey[Seq[File]]("Compiles with Scala js")
compileWithScalaJs := {
// call the correct compilation function / task on the Scala.js project
// copy the resulting javascript files to webTarget.value / "scalajs-plugin"
// return a list of those files
}
sourceGenerators in Assets <+= compileWithScalaJs
Edit
To create a plugin involves a bit more work. Scala.js is not yet an AutoPlugin, so you might have some problems with dependencies.
The first part is that you add the Scala.js library as a dependency to your plugin. You can do that by using code like this:
libraryDependencies += Defaults.sbtPluginExtra(
"org.scala-lang.modules.scalajs" % "scalajs-sbt-plugin" % "0.5.5",
(sbtBinaryVersion in update).value,
(scalaBinaryVersion in update).value
)
Your plugin would look something like this:
object MyScalaJsPlugin extends AutoPlugin {
/*
Other settings like autoImport and requires (for the sbt web dependency),
see the link above for more information
*/
def projectSettings = scalaJSSettings ++ Seq(
// here you add the sourceGenerators in Assets implementation
// these settings are scoped to the project, which allows you access
// to directories in the project as well
)
}
Then in a project that uses this plugin you can do:
lazy val root = project.in( file(".") ).enablePlugins(MyScalaJsPlugin)
Have a look at sbt-play-scalajs. It is an additional sbt plugin that helps integration with Play / sbt-web.
Your build file will look something like this (copied from README):
import sbt.Project.projectToRef
lazy val jsProjects = Seq(js)
lazy val jvm = project.settings(
scalaJSProjects := jsProjects,
pipelineStages := Seq(scalaJSProd)).
enablePlugins(PlayScala).
aggregate(jsProjects.map(projectToRef): _*)
lazy val js = project.enablePlugins(ScalaJSPlugin, ScalaJSPlay)