How to get the dependencies of all sub-projects from a SBT project, in a SBT task? - scala

I'm write a SBT task, which can output the dependencies information, grouped by project (say a SBT project has multi projects)
I know there is a sbt-dependency-graph plugin, but I can use it directly, because I want to generate a json file, but that plugin just output the dependency tree to console, without returning an data object, I can't easily get the data I want.
I found the update task returns a UpdateReport which contains a lot of information I want, but it only belong to the current project. In command line, if I want to know the information of all project, I can manually show all the projects by projects command, and view them one by one by someproject/update.
But how to do the same in a SBT task? I tried:
val reports = projects.toList.map(prj => (update in prj).value)
It reports:
[error] /Users/me/workspace/sbt-test/project/Build.scala:51: Illegal dynamic reference: prj
[error] val reports = projects.toList.map(prj => (update in prj).value)
[error] ^
[error] one error found
How to fix it?
More code:
import sbt._
import sbt.Keys._
object DemoBuild extends Build {
lazy val allUpdate = taskKey[Unit]("show update reports of all projects")
lazy val core = project
lazy val web = project
lazy val allUpdateDef = allUpdate := {
val reports = projects.toList.map(prj => (update in prj).value)
println(reports)
}
lazy val root = (project in file("."))
.settings(
allUpdateDef
)
}

After checking the document: http://www.scala-sbt.org/0.13/docs/Tasks.html, I found the solution:
import sbt._
import sbt.Keys._
object DemoBuild extends Build {
lazy val groupByProject: Def.Initialize[Task[(String, UpdateReport)]] =
Def.task {
(thisProject.value.id, (update in thisProject).value)
}
lazy val filter = ScopeFilter(inAnyProject, inAnyConfiguration)
updateByProject := {
val subProjects = groupByProject.all(filter).value.map { case ( projectName, updateReport) =>
...
}
}
}

Related

Why does custom scaladoc task throw MissingRequirementError: object scala.annotation.Annotation in compiler mirror not found?

I hit a MissingRequirementError when I try to invoke scaladoc from within an sbt task.
Using any version of sbt 0.13.x, start with this build.sbt:
val scaladoc = taskKey[Unit]("run scaladoc")
scaladoc := {
import scala.tools.nsc._
val settings = new doc.Settings(error => print(error))
settings.usejavacp.value = true
val docFactory = new doc.DocFactory(new reporters.ConsoleReporter(settings), settings)
val universe = docFactory.makeUniverse(Left((sources in Compile).value.map(_.absolutePath).toList))
}
Then run sbt scaladoc, and behold (during makeUniverse):
[info] Set current project to test (in build file:...)
scala.reflect.internal.MissingRequirementError: object scala.annotation.Annotation in compiler mirror not found.
at scala.reflect.internal.MissingRequirementError$.signal(MissingRequirementError.scala:16)
at scala.reflect.internal.MissingRequirementError$.notFound(MissingRequirementError.scala:17)
at scala.reflect.internal.Mirrors$RootsBase.getModuleOrClass(Mirrors.scala:48)
What is wrong here? I've already tried fork := true and different combinations of sbt/scala versions to no avail.
It seems you need to provide scala-library (and indeed, any other dependencies) directly to the DocFactory.
scaladoc := {
import scala.tools.nsc._
val settings = new doc.Settings(error => print(error))
val dependencyPaths = (update in Compile).value
.select().map(_.absolutePath).mkString(java.io.File.pathSeparator)
settings.classpath.append(dependencyPaths)
settings.bootclasspath.append(dependencyPaths)
val docFactory = new doc.DocFactory(new reporters.ConsoleReporter(settings), settings)
val universe = docFactory.makeUniverse(Left((sources in Compile).value.map(_.absolutePath).toList))
}

SBT 0.13 Build.scala References to undefined settings

I am new to SBT and I have been trying to build a custom task for this build.
I have a simple build project:
import sbt._
import Keys._
object JsonBuild extends Build{
lazy val barTask = taskKey[Unit]("some simple task")
val afterTestTask1 = barTask := { println("tests ran!") }
val afterTestTask2 = barTask <<= barTask.dependsOn(test in Test)
lazy val myBarTask = taskKey[Unit]("some simple task")
//val afterMyBarTask1 = myBarTask := { println("tests ran!") }
lazy val afterMyBarTask2 = myBarTask <<= (myBarTask).dependsOn(test in Test) map { _ => println("tests ran!") }
//settings ++ Seq(afterMyBarTask2)
override lazy val settings = super.settings ++ Seq(afterMyBarTask2)
}
I keep getting the error:
References to undefined settings:
{.}/*:myBarTask from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
{.}/test:test from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
Did you mean test:test ?
I have googled around and I cannot find a solution.
Can you explain why it is not working?
lazy val myBarTask = taskKey[Unit]("some simple task")
override lazy val settings = super.settings ++ Seq(myBarTask := { (test in Test).value; println("tests ran!") } )
myBarTask is undefined when you call dependsOn. you should define it before using dependsOn. also value call on key (task/setting) is now preferred way to depend on other keys. you can still use your version, but define myBarTask
This has been bothering.
I did a bit more reading.
I think I know why the above code does not work.
lazy val afterMyBarTask2 = myBarTask <<= (myBarTask).dependsOn(test in Test) map { _ => println("tests ran!") }
When I write (myBarTask).dependsOn(test in Test), the project scope for test is chosen by SBT as ThisBuild.
{.}/test:test from {.}/*:myBarTask (C:\Users\haques\Documents\workspace\SBT\jsonParser\project\Build.scala:13)
ThisBuild project scope does not have the setting test in configuration Test.
Only projects have the setting test.
The key I think that setting is added by some default SBT plugin to the projects settings.
You check what scopes settings exist in SBT by using the inspect command.
If you type in the SBT REPL:
{.}/test:test
The output is:
inspect {.}/test:test
[info] No entry for key.
SBT correctly suggests:
test:test which is:
{file:/C:/Users/haques/Documents/workspace/SBT/jsonParser/}jsonparser/test:test
If the project is not specified in the project scope axis, SBT chooses the current project by default.
Every SBT project if not specified has its own project settings.

SBT create sub-projects using a collection

I've been searching if this is possible for a while with little success.
Using SBT, can you create a sub-project programmatically, without explicitly assigning each project to it's own val?
My current project structure looks something like this:
root/
common/ <--- This is another sub-project that others dependOn
project/
build.scala
src/main/scala
apps/ <--- sub-projects live here
Sub1/
Sub2/
Sub1 and Sub2 are both their own SBT projects.
My first attempt to link these projects together looked like this:
// root/project/build.scala
import sbt._
import Keys._
object build extends Build {
lazy val common = project /* Pseudo-code */
val names = List("Sub1", "Sub2")
lazy val deps = names map { name =>
Project(id = name, base = file(s"apps/$name")).dependsOn(common)
}
lazy val finalDeps = common :: deps
lazy val root = project.in(file(".")).aggregate(finalDeps.map(sbt.Project.projectToRef) :_*)
.dependsOn(finalDeps.map(ClassPathDependency(_, None)) :_*)
}
However, because SBT uses reflection to build it's projects and sub-projects, this doesn't work.
It only works if each sub-project is stated explicitly:
lazy val Sub1 = project.in(file("apps/Sub1"))
So the question:
Is there a way to programmatically build sub-project dependencies in SBT?
Sbt allows for making a build definition for the build itself:
http://www.scala-sbt.org/release/docs/Getting-Started/Full-Def.html
You can try creating a project/project/build.scala file that contains a source generator, something like this:
// project/project/build.scala
sourceGenerators in Compile <+= sourceManaged in Compile map { out =>
Generator.generate(out / "generated")
}
EDIT: You should implement the Generator object yourself.
This source generator will in turn scan the topmost apps folder and create a source for an object that contains all the subprojects.
// project/subprojects.scala
// This is autogenerated from the source generator
object Subprojects{
lazy val Sub1 = project.in(file("apps/Sub1"))
lazy val Sub2 = project.in(file("apps/Sub2"))
lazy val all = Seq(Sub1,Sub2)
}
Now in your main build.scala just write:
// project/build.scala
lazy val root = project.in(file("."))
.aggregate(Subprojects.all.map(sbt.Project.projectToRef) :_*)
.dependsOn(Subprojects.all.map(ClassPathDependency(_, None)) :_*)
I didn't run all this through a compiler so some errors are possible but the principle should work.
EDIT: I created a repo on Github where I implemented the solution. Go there and see how it is done.
https://github.com/darkocerdic/sbt-auto-subprojects

Getting an SBT task to depend on the OneJar task

I'm having trouble getting my new SBT task 'install' to depend on the OneJar task. Here's my Build.scala file:
import sbt._
import Keys._
import com.github.retronym.SbtOneJar._
object BuildBroBuild extends Build {
val install = TaskKey[Unit]("install", "Installs the JAR and a launcher script into your homedir")
private def installTask = task {
println("Hello world!")
}
override lazy val settings = super.settings ++
Seq(install <<= (oneJar in Global)(installTask dependsOn(_)))
lazy val root = Project(id = "buildbro",
base = file("."),
settings = Project.defaultSettings)
}
And here's the error I'm getting:
[error] Reference to undefined setting:
[error]
[error] */*:one-jar from {.}/*:install
[error] Did you mean *:one-jar ?
[error]
Does anybody know what this means? I believe I have to scope the oneJar TaskKey in a different way. Thanks for any help you can offer.
I think something like this should work:
object BuildBroBuild extends Build {
val install = TaskKey[Unit]("install", "Installs the JAR and a launcher script into your homedir")
private lazy val installTask = install <<= (oneJar, streams) map { case (a, s) => {
// 'a' is the output from the onejar task (ie, the artifact)
println("Hello world!")
}
override lazy val settings = super.settings ++
Seq(installTask)
lazy val root = Project(id = "buildbro",
base = file("."),
settings = Project.defaultSettings)
}
Here, we are taking the output of the oneJar task (as well as streams, which allows for logging, etc) as input for our new task.

sbt Task classpath

I'm working on a sbt Task and I would like to have access to some of the application classes and dependencies.
(Specifically, I'd like to generate the Database DDL using scalaquery)
Is there any way to add those dependencies to the task or maybe I need to create a plugin for this?
object ApplicationBuild extends Build {
val appName = "test"
val appVersion = "1.0-SNAPSHOT"
val appDependencies = Seq(
"org.scalaquery" % "scalaquery_2.9.0-1" % "0.9.5")
val ddl = TaskKey[Unit]("ddl", "Generates the ddl in the evolutions folder")
val ddlTask = ddl <<= (baseDirectory, fullClasspath in Runtime) map { (bs, cp) =>
val f = bs / "conf/evolutions/default"
// Figures out the last sql number used
def nextFileNumber = { ... }
//writes to file
def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) { ...}
def createDdl = {
import org.scalaquery.session._
import org.scalaquery.ql._
import org.scalaquery.ql.TypeMapper._
import org.scalaquery.ql.extended.H2Driver.Implicit._
import org.scalaquery.ql.extended.{ ExtendedTable => Table }
import models._
printToFile(new java.io.File(nextFileNumber, f))(p => {
models.Table.ddl.createStatements.foreach(p.println)
});
}
createDdl
None
}
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
ddlTask)
}
The error I get is
[test] $ reload
[info] Loading global plugins from /home/asal/.sbt/plugins
[info] Loading project definition from /home/asal/myapps/test/project
[error] /home/asal/myapps/test/project/Build.scala:36: object scalaquery is not a member of package org
[error] import org.scalaquery.session._
[error] ^
[error] one error found
Thanks in advance
You have to add ScalaQuery and everything else your build depends on as a build dependency. That means that basically, you have to add it "as an sbt plugin".
This is described in some detail in the Using Plugins section of the sbt wiki. It all boils down to a very simple thing, though - just add a line defining your dependency under project/plugins.sbt like this:
libraryDependencies += "org.scalaquery" % "scalaquery_2.9.0-1" % "0.9.5"
Now, the problem with using application classes in the build is that you can't really add build products as build dependencies. - So, you would probably have to create a separate project that builds your DDL module, and add that as dependency to the build of this project.