Why does sbt console not see packages from subproject in multi-module project? - scala

This is my project/Build.scala:
package sutils
import sbt._
import Keys._
object SutilsBuild extends Build {
scalaVersion in ThisBuild := "2.10.0"
val scalazVersion = "7.0.6"
lazy val sutils = Project(
id = "sutils",
base = file(".")
).settings(
test := { },
publish := { }, // skip publishing for this root project.
publishLocal := { }
).aggregate(
core
)
lazy val core = Project(
id = "sutils-core",
base = file("sutils-core")
).settings(
libraryDependencies += "org.scalaz" % "scalaz-core_2.10" % scalazVersion
)
}
This seems to be compiling my project just fine, but when I go into the console, I can't import any of the code that just got compiled?!
$ sbt console
scala> import com.github.dcapwell.sutils.validate.Validation._
<console>:7: error: object github is not a member of package com
import com.github.dcapwell.sutils.validate.Validation._
What am I doing wrong here? Trying to look at the usage, I don't see a way to say which subproject to load while in the console
$ sbt about
[info] Loading project definition from /src/sutils/project
[info] Set current project to sutils (in build file:/src/sutils/)
[info] This is sbt 0.13.1
[info] The current project is {file:/src/sutils/}sutils 0.1-SNAPSHOT
[info] The current project is built against Scala 2.10.3
[info] Available Plugins: org.sbtidea.SbtIdeaPlugin
[info] sbt, sbt plugins, and build definitions are using Scala 2.10.3

There's the solution from #Alexey-Romanov to start the console task in the project the classes to import are in.
sbt sutils/console
There's however another solution that makes the root sutils project depend on the other core. Use the following snippet to set up the project - note dependsOn core that will bring the classes from the core project to sutils's namespace.
lazy val sutils = Project(
id = "sutils",
base = file(".")
).settings(
test := { },
publish := { }, // skip publishing for this root project.
publishLocal := { }
).aggregate(
core
).dependsOn core
BTW, you should really use a simpler build.sbt for your use case as follows:
scalaVersion in ThisBuild := "2.10.0"
val scalazVersion = "7.0.6"
lazy val sutils = project.in(file(".")).settings(
test := {},
publish := {}, // skip publishing for this root project.
publishLocal := {}
).aggregate(core).dependsOn(core)
lazy val core = Project(
id = "sutils-core",
base = file("sutils-core")
).settings(
libraryDependencies += "org.scalaz" %% "scalaz-core" % scalazVersion
)
You could make it even easier when you'd split the build to two build.sbts, each for the projects.

Related

How to create a generic SBT root project with varying sub projects

I'm working on the Scala track in Exercism which means I have a lot of SBT projects in a root folder. I'd like to create a root SBT project which will automatically add new sub-projects as I download new exercises. Currently I have to add them manually, so my root build.sbt looks like this:
lazy val root = (project in file("."))
.aggregate(
hello_world,
sum_of_multiples,
robot_name)
lazy val hello_world = project in file("hello-world")
lazy val sum_of_multiples = project in file("sum-of-multiples")
lazy val robot_name = project in file("robot-name")
...but I'd like to avoid having to add every project manually. Is there a way to add new projects automatically?
I'd like to avoid having to add every project manually. Is there a way to add new projects automatically?
Sure. It's a bit advanced use of sbt, but you can create an ad-hoc plugin that generates subprojects programmatically.
build.sbt
ThisBuild / scalaVersion := "2.12.8"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
ThisBuild / organizationName := "example"
project/build.properties
sbt.version=1.2.8
project/SubprojectPlugin.scala
import sbt._
object SubprojectPlugin extends AutoPlugin {
override val requires = sbt.plugins.JvmPlugin
override val trigger = allRequirements
override lazy val extraProjects: Seq[Project] = {
val dirs = (file(".") * ("*" -- "project" -- "target")) filter { _.isDirectory }
dirs.get.toList map { dir =>
Project(dir.getName.replaceAll("""\W""", "_"), dir)
}
}
}
Now if you start up sbt, any directories that are not named target or project will result to a subproject.
sbt:generic-root> projects
[info] In file:/private/tmp/generic-root/
[info] * generic-root
[info] hello_world
[info] robot_name
[info] sum_of_multiple
hello-world/build.sbt
To add further settings you can create build.sbt file under the directory as follows:
libraryDependencies += "commons-io" % "commons-io" % "2.6"

Scala sbt AutoPlugin with dependencies. Error while enablePlugins in another project

I'm trying to workaround creating sbt AutoPlugins.
I want to create plugin which will autoloading all his dependencies, so I use NoTrigger policy.
I wrote my own AutoPlugin which must execute assembly task from sbt-assembly and look like:
settings in /build.sbt
name := "sbt-myplugin"
version := "0.0.1"
organization := "com.org"
scalaVersion := "2.10.6"
sbtPlugin := true
sbtVersion := "0.13.11"
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
plugin code in /src/main/scala/myplugin/MyPlugin.scala
package myplugin
import sbt._
import sbtassembly.AssemblyPlugin
import sbtassembly.AssemblyPlugin.autoImport._
object MyPlugin extends AutoPlugin{
override def trigger = noTrigger
override def requires = AssemblyPlugin
object autoImport {
val myAssembly = taskKey[File]("Assembled file")
}
import autoImport._
override lazy val projectSettings = Seq(
myAssembly := assembly.value
)
}
Then i'm create artifact with sbt clean compile publishLocal
After this I created test project which will use my plugin.
settings for this project in /project/plugins.sbt
logLevel := Level.Warn
resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local"
addSbtPlugin("com.academmedia.ias" % "sbt-pkplace" % "0.0.1")
settings in /biuld.sbt
name := "test-project"
version := "1.0"
scalaVersion := "2.11.8"
lazy val root = (project in file(".")).enablePlugins(myplugin.MyPlugin)
Now I'm expecting able to use MyPlugin task myAssembly but my project sbt is unable to download project settings with error:
[error] java.lang.NoClassDefFoundError: sbtassembly/AssemblyPlugin$
What am I doing wrong?
Thanks for answer!

Adding module dependency information in sbt's build.sbt file

I have a multi module project in IntelliJ, as in this screen capture shows, contexProcessor module depends on contextSummary module.
IntelliJ takes care of everything once I setup the dependencies in Project Structure.
However, when I run sbt test with the following setup in build.sbt, I got an error complaining that it can't find the packages in contextSummary module.
name := "contextProcessor"
version := "1.0"
scalaVersion := "2.11.7"
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.2" % "test"
How to teach sbt that the missing modules are found?
I could use the build.sbt file in the main root directory.
lazy val root = (project in file(".")).aggregate(contextSummary, contextProcessor)
lazy val contextSummary = project
lazy val contextProcessor = project.dependsOn(contextSummary)
Reference: http://www.scala-sbt.org/0.13.5/docs/Getting-Started/Multi-Project.html
For testing only one project, I can use project command in sbt.
> sbt
[info] Set current project to root (in build file:/Users/smcho/Desktop/code/ContextSharingSimulation/)
> project contextProcessor
[info] Set current project to contextProcessor (in build file:/Users/smcho/Desktop/code/ContextSharingSimulation/)
> test
For batch mode as in How to pass command line args to program in SBT 0.13.1?
sbt "project contextProcessor" test
I think a simple build.sbt might not be enough for that.
You would need to create a more sophisticated project/Build.scala like that:
import sbt._
import sbt.Keys._
object Build extends Build {
lazy val root = Project(
id = "root",
base = file("."),
aggregate = Seq(module1, module2)
)
lazy val module1 = Project(
id = "module1",
base = file("module1-folder"),
settings = Seq(
name := "Module 1",
version := "1.0",
scalaVersion := "2.11.7",
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.2" % "test"
lazy val module2 = Project(
id = "module2",
base = file("module2-folder"),
dependencies = Seq(module1),
settings = Seq(
name := "Module 2",
version := "1.0",
scalaVersion := "2.11.7",
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "2.2.2" % "test"
}

How to use custom plugin in multi-module project?

In a multi-module project, with one module implementing a custom SBT plugin with custom TaskKey, how this plugin can be imported for the project settings of another submodule.
If the submodule using the plugin is define in submodule1/build.sbt, then submodule1/project/plugins.sbt is not loaded.
If plugin is registered in project/plugins.sbt it will fails when loading top/aggregate project as plugin is not necessarily already built.
Is there any other way to define a custom task requiring custom dependency so that it can be used by a submodule?
Here is how I finally make it works:
import sbt._
import Keys._
object MyBuild extends Build {
private lazy val myGenerator =
// Private project with generator code and its specific dependencies
// (e.g. Javassist)
Project(id = "my-generator",
base = file("project") / "my-generator").settings(
name := "my-generator",
javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
autoScalaLibrary := false,
scalacOptions += "-feature",
resolvers +=
"Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/releases/",
libraryDependencies ++= Seq( // Dependencies required to generate classes
"org.javassist" % "javassist" % "3.18.2-GA")
)
// Some custom setting & task
lazy val generatedClassDirectory = settingKey[File](
"Directory where classes get generated")
lazy val generatedClasses = taskKey[Seq[(File, String)]]("Generated classes")
lazy val myProject =
Project(id = "my-project", base = file("my-project")).settings(
name := "my-project",
javacOptions in Test ++= Seq("-Xlint:unchecked", "-Xlint:deprecation"),
autoScalaLibrary := false,
scalacOptions += "-feature",
libraryDependencies ++= Seq(/* ... */),
generatedClassDirectory := {
// Defines setting for path to generated classes
val dir = target.value / "generated_classes"
if (!dir.exists) dir.mkdirs()
dir
},
generatedClasses <<= Def.task { // Define task generating .class files
// first get classloader including generator and its dependencies
val cp = (fullClasspath in (myGenerator, Compile)).value
val cl = classpath.ClasspathUtilities.toLoader(cp.files)
// then loaded generator class, and instantiate with structural type
val genClass = cl loadClass "my.custom.GeneratorClass"
val generator = genClass.newInstance.
asInstanceOf[{def writeTo(out: File): File}]
// finally we can call the
val outdir = generatedClassDirectory.value
val generated = generator writeTo outdir
val path = generated.getAbsolutePath
// Mappings describing generated classes
Seq[(File, String)](generated -> path.
drop(outdir.getAbsolutePath.length+1))
} dependsOn(compile in (myGenerator, Compile))/* awkward? */,
managedClasspath in Compile := {
// Add generated classes to compilation classpath,
// so it can be used in my-project sources
val cp = (managedClasspath in Compile).value
cp :+ Attributed.blank(generatedClassDirectory.value)
},
// Make sure custom class generation is done before compile
compile in Compile <<= (compile in Compile) dependsOn generatedClasses,
mappings in (Compile, packageBin) := {
val ms = mappings.in(Compile, packageBin).value
ms ++ generatedClasses.value // add generated classes to package
}
).dependsOn(myGenerator/* required even if there dependsOn(compile in (myGenerator, Compile)) */)
}
Not sure there is better solution, especially about the redondant dependsOn(compile in (myGenerator, Compile)) and .dependsOn(myGenerator).

Integrating scala code coverage tool jacoco into a play 2.2.x project

My goal is to integrate jacoco into my play 2.2.0 project.
Different guides on the web I tried to follow mostly added to confusion not to closing in on the goal.
Adding to confusion
Most guides assume the existance of an build.sbt
which as it seems as been superseded by an build.scala with a different
There is a jacoco4sbt and a regular jacoco
which one is most appropiate for use with scala play framework 2
Current state
in plugins.sbt added
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.2")
in build.scala added
import de.johoop.jacoco4sbt.JacocoPlugin._
lazy val jacoco_settings = Defaults.defaultSettings ++ Seq(jacoco.settings: _*)
With these changes i don't get an "jacoco" task in sbt nor in the play console.
What are the appropriate steps to get this working?
Update
As requested the content of the build.scala
import com.typesafe.sbt.SbtNativePackager._
import com.typesafe.sbt.SbtScalariform._
import play.Project._
import sbt.Keys._
import sbt._
import sbtbuildinfo.Plugin._
import de.johoop.jacoco4sbt.JacocoPlugin._
object BuildSettings {
val buildOrganization = "XXXXX"
val buildVersion = "0.1"
val buildScalaVersion = "2.10.2"
val envConfig = "-Dsbt.log.format=false -Dconfig.file=" + Option(System.getProperty("env.config")).getOrElse("local.application")
scalacOptions ++= Seq("-encoding", "UTF-8", "-deprecation", "-unchecked", "-feature")
javaOptions += envConfig
val buildSettings = Defaults.defaultSettings ++ Seq (
organization := buildOrganization,
version := buildVersion,
scalaVersion := buildScalaVersion
)
}
object Resolvers {
val remoteRepoUrl = "XXXXXXXXXXXX" at "http://nexus.cXXXXX/content/repositories/snapshots/"
val publishRepoUrl = "XXXXXXXXXXXX" at "http://nexus.ciXXXXXXXXXXXXXXXX/content/repositories/snapshots/"
}
object Dependencies {
val ods = "XXXXXXXXX" % "XXXXXX-ws" % "2.2.1-SNAPSHOT"
val scalatest = "org.scalatest" %% "scalatest" % "2.0.M8" % "test"
val mockito = "org.mockito" % "mockito-all" % "1.9.5" % "test"
}
object ApplicationBuild extends Build {
import BuildSettings._
import Dependencies._
import Resolvers._
// Sub-project specific dependencies
val commonDeps = Seq(
ods,
scalatest,
mockito
)
//val bN = settingKey[Int]("current build Number")
val gitHeadCommitSha = settingKey[String]("current git commit SHA")
val release = settingKey[Boolean]("Release")
lazy val jacoco_settings = Defaults.defaultSettings ++ Seq(jacoco.settings: _*)
lazy val nemo = play.Project(
"nemo",
path = file("."),
settings = Defaults.defaultSettings ++ buildSettings ++
Seq(libraryDependencies ++= commonDeps) ++
Seq(scalariformSettings: _*) ++
Seq(playScalaSettings: _*) ++
buildInfoSettings ++
jacoco_settings ++
Seq(
sourceGenerators in Compile <+= buildInfo,
buildInfoKeys ++= Seq[BuildInfoKey](
resolvers,
libraryDependencies in Test,
buildInfoBuildNumber,
BuildInfoKey.map(name) { case (k, v) => "project" + k.capitalize -> v.capitalize },
"envConfig" -> envConfig, // computed at project load time
BuildInfoKey.action("buildTime") {
System.currentTimeMillis
} // re-computed each time at compile
),
buildInfoPackage := "com.springer.nemo"
) ++
Seq(resolvers += remoteRepoUrl) ++
Seq(mappings in Universal ++= Seq(
file("ops/rpm/start-server.sh") -> "start-server.sh",
file("ops/rpm/stop-server.sh") -> "stop-server.sh"
))
).settings(version <<= version in ThisBuild)
lazy val nemoPackaging = Project(
"nemoPackaging",
file("nemoPackaging"),
settings = Defaults.defaultSettings ++Seq(Packaging.settings:_*)
)
def publishSettings =
Seq(
publishTo := Option(publishRepoUrl),
credentials += Credentials(
"Repo", "http://mycompany.com/repo", "admin", "admin123"))
}
Note: jacoco is running with this but does not pick up our tests. Output:
jacoco:cover
[info] Compiling 1 Scala source to /home/schl14/work/nemo/target/scala-2.10/classes...
[info] ScalaTest
[info] Run completed in 13 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] No tests to run for nemo/jacoco:test
I solved it by doing this.
Add the following to plugins.sbt
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.2")
In build.scala i added a new import
import de.johoop.jacoco4sbt.JacocoPlugin._
and added jacoco to the config section like this
lazy val xyz = play.Project(
"xyz",
path = file("."),
settings = Defaults.defaultSettings
jacoco.settings ++ //this is the important part.
).settings(parallelExecution in jacoco.Config := false) //not mandatory but needed in `most cases as most test can not be run in parallel`
After these steps jacoco:cover was available in the sbt and play console and also discovers our tests.
A better option for Scala code coverage is Scoverage which gives statement line coverage.
https://github.com/scoverage/scalac-scoverage-plugin
Add to project/plugins.sbt:
addSbtPlugin("com.sksamuel.scoverage" % "sbt-scoverage" % "1.0.1")
Then run SBT with
sbt clean coverage test
To measure the code coverage of Java code jacoco4sbt is the best fit.
Add to project/plugins.sbt:
addSbtPlugin("de.johoop" % "jacoco4sbt" % "2.1.2")
Add at the end of build.sbt:
jacoco.settings
Then run in the terminal:
//or just the sbt command and then use your browser
sbt jacoco:cover && /usr/bin/x-www-browser target/scala-2.10/jacoco/html/index.html
Scala code coverage can also be determined by SCCT.
Add to project/plugins.sbt:
addSbtPlugin("com.github.scct" % "sbt-scct" % "0.2.1")
Add at the end of build.sbt:
ScctPlugin.instrumentSettings
And then to see the coverage:
sbt scct:test && /usr/bin/x-www-browser target/scala_2.10/coverage-report/index.html
Maybe you get the error
Please either restart the browser with --allow-file-access-from-files
or use a different browser.
Maybe you use chrome and the security settings forbid dynamic actions on local files. You can open the page with firefox or use python -m SimpleHTTPServer 8000 to bind it to the http protokoll and open http://localhost:8000/target/scala-2.10/coverage-report/ .
Inspiration which generated classes should be excluded from the report can you find on the mailing list.
My test projects are on GitHub.