scala.io.source.fromFile with sbt subProjects reading from root resource folder - scala

I have a sbt build with multiple subProjects, when trying to use scala.io.source.fromFile it tries to read files from the root project and not from each individual subProject. This is what I need in some cases but in others I want to use the root resource.
What is the idiomatic way of reading from subproject and root resource folders in SBT with subprojects?

It depends on which scope you are calling resourceDirectory from. Here is an example of build with one root project and two sub-projects (from sbt REPL):
> resourceDirectory
[info] alpha/compile:resourceDirectory
[info] /Users/xyz/sbt-tutorial/sub-projects/sub-a/src/main/resources
[info] beta/compile:resourceDirectory
[info] /Users/xyz/sbt-tutorial/sub-projects/sub-b/src/main/resources
[info] root/compile:resourceDirectory
[info] /Users/xyz/sbt-tutorial/sub-projects/src/main/resources
As you can see, calling alpha/compile:resourceDirectory will get you the resource directory for the sub-project called alpha.
If you want the sbt DSL notation for this it will be:
myTask := {
val resDir = (resourceDirectory in (alpha, Compile)).value
...
}
You can replace alpha etc. with ThisBuild if you want to get the resource directory for the root project.

Related

SBT aggregating subproject

Given a project structure:
rootProject
aggregatingSubProject
subA
subB
subC
How do I run the package task for aggregatingSubProject and all its aggregated projects, without specifying them by hand?
So far I can only have subA and subB built when doing package in the rootProject - regardless if I put .aggregate(subA, subB) in aggregatingProject's build.sbt.
I need the rootProject to define common settings for the build and I want to build multiple projects (some aggregating other projects, like aggregatingSubProject does) in a single build.
EDIT: I need to do this without specifying all the sub-sub-projects in the root build.sbt. I'd like to define subA and subB in aggregatingSubProject/build.sbt.
Using sbt 0.13.16.
Given this in build.sbt:
val rootProject = project in file(".")
val aggregatingSubProject = project
val subC = project
and this in aggregatingSubProject/build.sbt:
aggregateProjects(subA, subB)
val subA = project
val subB = project
You can run aggregatingSubProject/package and get:
> aggregatingSubProject/package
[info] Updating {file:/s/t-subaggregate/}subB...
[info] Updating {file:/s/t-subaggregate/}aggregatingSubProject...
[info] Updating {file:/s/t-subaggregate/}subA...
[info] Done updating.
[info] Packaging /s/t-subaggregate/aggregatingSubProject/subA/target/scala-2.12/suba_2.12-0.1-SNAPSHOT.jar ...
[info] Done updating.
[info] Done packaging.
[info] Packaging /s/t-subaggregate/aggregatingSubProject/subB/target/scala-2.12/subb_2.12-0.1-SNAPSHOT.jar ...
[info] Done packaging.
[info] Done updating.
[info] Packaging /s/t-subaggregate/aggregatingSubProject/target/scala-2.12/aggregatingsubproject_2.12-0.1-SNAPSHOT.jar ...
[info] Done packaging.

How to define build-scoped settings in multi-project .sbt builds?

As of sbt 0.13, the recommended way to define all configuration is multi-project .sbt build definition. It is absolutely clear how to define settings for each project there; however, how one should define build-wide settings, for example, in ThisBuild or Global scopes?
With Scala build definitions it is clear: we have settings key coming from Build trait:
object MyBuild extends Build {
override lazy val settings = super.settings ++ Seq(
someKey := someValue
)
}
Now someKey's value will be build-scoped by default. Alternatively, it can be defined directly in .sbt file with ThisBuild scope:
someKey in ThisBuild := someValue
However, there is no natural place to define these options in multi-project .sbt builds. I suspect that they should be defined in one of the projects with ThisBuild scope:
val someProject = project
.settings(someKey in ThisBuild := someValue)
but this is very counterintuitive and not really clear: are such settings really applied globally or do they really belong to the specific project? What if I define the same key in multiple projects?
val project1 = project.settings(someKey in ThisBuild := someValue1)
val project2 = project.settings(someKey in ThisBuild := someValue2)
The documentation does not give any answers on this, unfortunately.
Note that I explicitly do not ask how to share global settings between builds or projects. I ask specifically about defining build-scoped settings.
Well, I've performed some experiments and found out the following. Here is an example build.sbt:
val testKey = settingKey[String]("A key for test")
testKey := "value in build.sbt"
testKey in ThisBuild := "build-scoped value in build.sbt"
testKey in Global := "global value in build.sbt"
lazy val root = (project in file("."))
.settings(
testKey := "value in root project",
testKey in ThisBuild := "build-scoped value in root project",
testKey in Global := "global value in root project"
)
lazy val child = project
.settings(
testKey := "value in child",
testKey in ThisBuild := "build-scoped value in child project",
testKey in Global := "global value in child project"
)
and here's what I see in SBT console:
[info] Set current project to root (in build file:/private/tmp/sbtt/)
> inspect testKey
[info] Setting: java.lang.String = value in build.sbt
[info] Description:
[info] A key for test
[info] Provided by:
[info] {file:/private/tmp/sbtt/}root/*:testKey
[info] Defined at:
[info] /private/tmp/sbtt/build.sbt:4
[info] Delegates:
[info] root/*:testKey
[info] {.}/*:testKey
[info] */*:testKey
[info] Related:
[info] */*:testKey
[info] {.}/*:testKey
[info] child/*:testKey
> testKey
[info] value in build.sbt
> root/testKey
[info] value in build.sbt
> child/testKey
[info] value in child
> {.}/testKey
[info] global value in child
> reload
[info] Loading global plugins from /Users/netvl/.sbt/0.13/plugins
[info] Set current project to root (in build file:/private/tmp/sbtt/)
> testKey
[info] value in build.sbt
> root/testKey
[info] value in build.sbt
> child/testKey
[info] value in child
> {.}/testKey
[info] build-scoped value in child project
> */testKey
[info] global value in child project
So, the conclusion is as follows.
Values defined at the topmost level in a multi-project SBT build seem to be scoped to the project build.sbt belongs to. That is, here build.sbt resides in the root directory, and so defining testKey in the file directly is the same as defining it in (project in file(".")).settings(...) clause. According to SBT output, they also take priority over those defined in .settings(...).
Keys in larger scopes like ThisBuild or Global seem to be able to be defined in any project, but "later" projects will then take priority. This is probably related to the order the projects are processed. I have expected that the order of evaluation of lazy vals would affect this, and I've tried adding .aggregate(child) to the root project to force the child project to be evaluated first, but it didn't seem to have any effect: globally scoped values from child project still have priority.
Therefore, it seems fine to define build-scoped or globally scoped values inside the root project or inside common settings list which is then used in every project. However, assigning different values to a key in build or global scope in different projects will likely lead to a weird behavior - only one of these values will take effect, and which one I don't know.
It would be very nice if all this were explained in the documentation, but at the moment I can't find anything on this behavior there.
I'd gladly accept someone else's answer which would provide more insight on how SBT works in this regard.

Running a sub-project main class

I have a built.sbt that references a child project's main class as its own main class:
lazy val akka = (project in file("."))
.aggregate(api)
.dependsOn(api)
.enablePlugins(JavaAppPackaging)
lazy val api = project in file("api")
scalaVersion := "2.11.6"
// This is referencing API code
mainClass in (Compile, run) := Some("maslow.akka.cluster.node.ClusterNode")
artifactName := { (sv: ScalaVersion, module: ModuleID, artifact: Artifact) =>
s"""${artifact.name}.${artifact.extension}"""
}
name in Universal := name.value
packageName in Universal := name.value
However, each time I run sbt run I get the following error:
> run
[info] Updating {file:/Users/mark/dev/Maslow-Akka/}api...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Updating {file:/Users/mark/dev/Maslow-Akka/}akka...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Running maslow.akka.cluster.node.ClusterNode
[error] (run-main-0) java.lang.ClassNotFoundException: maslow.akka.cluster.node.ClusterNode
java.lang.ClassNotFoundException: maslow.akka.cluster.node.ClusterNode
at java.lang.ClassLoader.findClass(ClassLoader.java:530)
As I've been doing some research into the problem, I first switched to the project to api from akka and then opened up console. From there, it can't find the maslow package even though it most certainly exists. After that, I went into the api folder and ran sbt console and it accessed the aforementioned package just fine. After I do this, sbt run from the akka project works. Why?
The folder api is pulled in via git read-tree. There shouldn't be anything special about it. I'm using sbt 0.13.5
I think in a multi-project build a global line such as
mainClass in (Compile, run) := ...
will just be swallowed without consequences, as it doesn't refer to any project.
Probably the following works:
mainClass in (Compile, run) in ThisBuild := ...
Or you add it to the root project's settings:
lazy val akka = (project in file("."))
.aggregate(api)
.dependsOn(api)
.enablePlugins(JavaAppPackaging)
.settings(
mainClass in (Compile, run) := ...
)
The problem was this line: lazy val api = project in file("api")
From the docs:
When defining a dependency on another project, you provide a ProjectReference. In the simplest case, this is a Project object. (Technically, there is an implicit conversion Project => ProjectReference) This indicates a dependency on a project within the same build.
This indicates a dependency within the same build. Instead, what I needed was to use RootProject since api is an external build:
It is possible to declare a dependency on a project in a directory separate from the current build, in a git repository, or in a project packaged into a jar and accessible via http/https. These are referred to as external builds and projects. You can reference the root project in an external build with RootProject:
In order to solve this problem, I removed the project declarations out of build.sbt into project/Build.scala:
import sbt._
object MyBuild extends Build {
lazy val akka = Project("akka", file(".")).aggregate(api).dependsOn(api)
lazy val api = RootProject(file("api"))
}
To be clear, the problem was that my api sub-project was a ProjectRef and not a RootProject.

sbt 0.13.1 multi-project module not found when I change the sbt default scala library to scala 2.11.2

I use the sbt 0.13.1 create the two modules, and I create project/MyBuild.scala to compile this two modules.
MyBuild.scala:
import sbt._
import Keys._
object MyBuild extends Build {
lazy val task = project.in(file("task"))
lazy val root = project.in(file(".")) aggregate(task) dependsOn task
}
When I change the scala library to 2.11.2 by set scalaHome. It will go to maven download the task.jar and failed, that's very strange. Is it a sbt bug?
There is the github test project address: test-sbt-0.13.1
This is because you have defined a custom scalaVersion in your main build.sbt which means it is defined for the root project only. The task project will use the default value:
jjst#ws11:test-sbt-0.13.1$ sbt
[...]
> projects
[info] In file:/home/users/jjost/dev/test-sbt-0.13.1/
[info] * root
[info] task
> show scalaVersion
[info] task/*:scalaVersion
[info] 2.10.4
[info] root/*:scalaVersion
[info] 2.11.2-local
As a consequence, artifacts generated by the task subproject won't be available for the root project to use.
You can solve this by making sure that your projects use the same scalaVersion. The easiest way to do so while keeping the same project structure would be to share common settings like the scala version across projects like so:
object MyBuild extends Build {
val commonSettings = Seq(
scalaVersion := "2.11.2-local",
scalaHome := Some(file("/usr/local/scala-2.11.2/"))
)
lazy val task = (project.in(file("task"))).
settings(commonSettings: _*)
lazy val root = (project.in(file("."))).
settings(commonSettings: _*).
aggregate(task).
dependsOn(task)
}
In practice, you might want to put common settings shared between projects in a dedicated file under project/common.scala, as recommended in Effective sbt.
I'd recommend you to move the sources of root project from root to subfolder (task2), and add them as aggregated. It will remove aggregating and depending on same project:
object MyBuild extends Build {
lazy val task = project.in(file("task"))
lazy val task2 = project.in(file("task2")) dependsOn task
lazy val root = project.in(file(".")) aggregate(task, task2)
}
This works for me, but it's strange that such non-standard project structure works fine with default scalaHome. Seems to be a problem in the way in which sbt resolves such dependency as external.
P.S. This answer doesn't cover the whole story. See #jjst's answer to clarify more.

Why sbt-idea doesn't generate the IDEA module for my build project

I have a multi module SBT project with this structure:
➜ Stample git:(split-in-sbt-modules) ✗ tree -L 1
.
├── project
├── stample-core
├── stample-search
├── stample-web
└── target
The build definition is pretty simple and looks like:
lazy val stampleWebProject = play.Project("stample-web", appVersion, appDependencies,path = file("stample-web"))
.dependsOn(stampleCoreProject,stampleSearchProject)
.aggregate(stampleCoreProject,stampleSearchProject)
lazy val stampleCoreProject = Project(id = "stample-core",base = file("stample-core"))
lazy val stampleSearchProject = Project(id = "stample-search",base = file("stample-search"))
I'd like to know why when I generate the IDEA project files with sbt-idea plugin, the build definition /project is not imported as a module.
It seems it is supposed to be, and I am pretty sure that I already saw it working on my own project but can't make it work again :( By the way, for newly created Play2 projects, when we use the plugin to generate the IDEA files, the project is also imported as an IDEA module with the appropriate classpath.
This issue seems say it is the expected default behavior.
I use v 1.5.1 but tried an older version and it doesn't work either.
When I run the gen-idea command I get:
[info] Excluding folder .ideacalaz-core_2.10;7.0.0 ...
[info] Excluding folder .idea_modules
[info] Excluding folder target
[info] Excluding folder target
[info] Created /home/sebastien/Bureau/Stample/.idea/IdeaProject.iml
[info] Created /home/sebastien/Bureau/Stample/.idea
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-core/.idea
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-core/.idea_modules
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-core/target
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-core/target
[info] Created /home/sebastien/Bureau/Stample/.idea_modules/stample-core.iml
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-search/.idea
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-search/.idea_modules
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-search/target
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-search/target
[info] Created /home/sebastien/Bureau/Stample/.idea_modules/stample-search.iml
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/.idea
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/.idea_modules
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/cache
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/cache
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/classes
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/classes
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/classes_managed
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/classes_managed
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/native_libraries
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/native_libraries
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/resolution-cache
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/resolution-cache
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/resource_managed
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.10/resource_managed
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.9.2
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/scala-2.9.2
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/streams
[info] Excluding folder /home/sebastien/Bureau/Stample/stample-web/target/streams
[info] Created /home/sebastien/Bureau/Stample/.idea_modules/stample-web.iml
Can someone explain my why the project module is not created?
So in the end, it seems the plugin doesn't generate the build definition module if the parent folder is not included as an SBT project.
Adding this line seems to fix all my problems:
lazy val stampleRootProject = Project(id = "stample",base = file("."))