sbt : Is there a better way of structuring large build.sbt files - scala

Problem description: large sbt file
Our current build.sbt spanns 250+ lines.
We have two problems:
readability
current approach grouping of data and comments:
// Plugins ///////////////////////////////////////////////////
enablePlugins(DockerPlugin)
// basic configuration : projects ///////////////////////////
name := """projectName"""
lazy val projectName =
(project in file(".")).....
logic reuse
We have some configuration logic we would like to share between different projects.
Question
Is there a way to include other *.sbt files?
Or do you have a suggestion how to solve this problem without resorting to write a sbt plugin?

Is there a way to include other *.sbt files?
Yes, you can simply put parts of your build.sbt file into other *.sbt files within your project root. SBT picks up all *.sbt files and merges them together, as there was only a single large file.

One thing you can do is to factor out parts of your build info into scala files in the project directory.
E.g. in our build we have a file Dependencies.scala in the project directory, which contains all dependencies of the various projects in one place:
object Dependencies {
val akka_actor = "com.typesafe.akka" %% "akka-actor" % "2.3.13"
// ...
}
This can then be imported from the build.sbt:
import Dependencies._
lazy val foo = Project(...) dependsOn (akka_actor, ...)
You can also put tasks and commands into objects in the project directory.
Update: One thing I often do when looking for inspiration about how to organize a build is to look at the build of complex, high-profile scala projects such as akka. As you can see, they have moved a lot of logic into scala files in the project directory. The build itself is defined in AkkaBuild.scala.

Related

Scala "not found: object com" - should i really add entry in build.sbt if there are no other dependencies?

I have created basic Scala Play application with https://www.playframework.com/getting-started play-scala-seed. This project compiles and runs with sbt run. But I have another Scala project that compiles and runs and which I have submitted to my local Ivy repository with command sbt publishLocal. This other project was saved at C:\Users\tomr\.ivy2\local\com.agiintelligence\scala-isabelle_2.13\master-SNAPSHOT as a result of this command.
Then I imported (exactly so - imported, no just opened) my Play project in IntelliJ and I used Project - Open Module Settings - Project Settings - Libraries to add com.agiintelligence jar from my ivy2 location. After such operations IntelliJ editor recognizes com.agiintelligence classes. That is fine.
But when I am trying to run my Play application with sbt run, I experience the error message not found: object com that is exactly when compiling import com.agiintelligence line in my Scala controller file of Play application.
Of course - such error has been reported and resolved with, e.g. object play not found in scala application
But that solution suggests to append build.sbt file. My build.sbt file is pretty bare:
name := """agiintelligence"""
organization := "com.agiintelligence"
version := "1.0-SNAPSHOT"
lazy val root = (project in file(".")).enablePlugins(PlayScala)
scalaVersion := "2.13.5"
libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test
// Adds additional packages into Twirl
//TwirlKeys.templateImports += "com.skaraintelligence.controllers._"
// Adds additional packages into conf/routes
// play.sbt.routes.RoutesKeys.routesImport += "com.skaraintelligence.binders._"
My Play application contains (as can bee seen from the IntelliJ project pane) some tens of 'external libraries' (it shows my com.agiintelligence jar as well), but why should I add my own ivy2 library in build.sbt file if no other libraries are listed here? What is different with my library? It is on my computer, in the repository as expected already?
Of course, I can try to add it build.sbt and issue sbt update and see what happens, but I can not understand this logic? Can someone explain it and provide some clue to intelligible solution of my error message?
My Play application contains (as can bee seen from the IntelliJ project pane) some tens of 'external libraries'
Those are probably just transitive dependencies of your Play dependency, that is why sbt downloaded all of them and put them in your classpath so you could use them without you needing to tell it about them; because the pom of Play already did.
It is not that the build tool or the IDE magically added all those dependencies for you because they read your mind and magically understood you wanted them. And that for some reason the magic stopped working for your own library.
Why it is not sufficient to list it Project-Setting--External Libraries in IntelliJ only?
That is sufficient for the IDE to work, but not for the build tool. The build tool is independent of the IDE; it doesn't know about it. sbt just knows about the dependencies you configured in your definition file.
Even more, you should always configure your dependencies on your build tool and then import that in the IDE; rather than the opposite. IDEs are graphical tools, so their state can not be committed, can not be shared, can not keep track of changes, can not be used in CI / CD environments; additionally, different teammates may want to use different IDEs.
I resolved the error message by adding line in build.sbt file
libraryDependencies += "de.unruh" %% "scala-isabelle" % "master-SNAPSHOT"
and by subsequent run of sbt update.
Error is solved, but the main question still stand - why I had to do this? Why there are tens of dependencies that are not listed in build.sbt and why should I list my dependency in build.sbt and why it is not sufficient to list it Project-Setting--External Libraries in IntelliJ only?
OK, comment by #Luis_Miguel_Mejía_Suárez gave the explanation, that comment is the actual and expected answer to my question.

scala / sbt: How to build two JARs with identical content, except one JAR should exclude .conf files from resources

We are using sbt as build tool for our scala project.
My question is how to build two JARs with identical content, except one JAR should exclude .conf files from resources?
I tried it with
mappings in (Compile, packageBin) ~= { _.filter(!_._1.getName.endsWith(".conf")) }
i found here on stackoverflow but it will exclude the conf files in general.
I am new to scala and sbt, I read about multi module/project, maybe this can help?

The meaning of _root_ in sbt

I have two separate Scala projects. In both projects, the build.sbt has the line:
lazy val V = _root_.scalafix.sbt.BuildInfo
The two projects contain several projects so both build.sbt files are multi-project builds. In my research, I came across the idea of a root project and I think _root_ in the above value denotes the root project.
My problem is that in one of the projects, sbt cannot resolve the symbol scalafix in lazy val V = _root_.scalafix.sbt.BuildInfo. When I search scalafix.sbt.BuildInfo in this project, I can find the related jar but sbt somehow misses it. The two projects in so far as I can tell are almost identical but one resolves scalafix while the other does not.
For both projects, the jar seems to be located at the below directory in my Mac:
/Users/soft/.ivy2/cache/scala_2.12/sbt_1.0/ch.epfl.scala/sbt-scalafix/jars/sbt-scalafix-0.6.0-M19.jar!/scalafix/sbt
What is _root_ and how can I help sbt resolve scalafix?
I think root in the above value denotes the root project.
No, this is not true.
All sbt files are scala code and _root_ has the same meaning as _root_ in scala. Thus the answer is the same as in What is the root package in Scala.
The two projects in so far as I can tell are almost identical but one resolves scalafix while the other does not.
The problem is most probably the missing sclafix plugin.
Check that the plugin is enabled in project/plugins.sbt with line
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.1")

How do I use shared configurations across SBT (Play) multi-projects?

I have several SBT 0.13 / Play 2.2 projects (websites). They are all multi-module as they share some common functionality. This makes their project configuration files both complex and almost identical, but not quite.
I would like to be able to share as much as possible of these configuration files across the projects (frequent play updates makes keeping 5+ websites up to date a royal pain, not to mention all the almost-identical-but-evolving dependency lists across the projects).
build.properties and plugins.sbt are identical across projects and can be overwritten by a simple script. Great.
Build.scala is trickier - I would like to introduce a shared base class like so:
abstract class MyBuildBase extends Build { ... }
that in Build.scala do:
object ApplicationBuild extends MyBuildBuild { ... }
In order for this to make any sense at all, MyBuildBase.scala needs to be shared across projects. This can be done with svn:external, which operates on directories. Which means I need to somehow make this shared directory accessible when Build.scala is compiled (otherwise sbt complains loudly).
Reading http://www.scala-sbt.org/0.13.0/docs/Detailed-Topics/Classpaths.html and http://www.scala-sbt.org/0.13.0/docs/Getting-Started/Full-Def.html it seems like this should be possible.
However, it is exceptionally unclear to me what to actually put in the project/project/Build.scala file to actually achieve this - I can't find an example of "an sbt build file that's intended to build an sbt build file and include some extra source files in the build".
Any suggestions?
What you probably want to do is create a plugin, or shared library.
You can make an sbt project with a build like follows:
build.sbt
sbtPlugin := true
organization := "you"
name := "common-build"
version := "1.0"
Then create in src/main/scala your abstract class "MyBuildBase". Release this project as an sbt plugin.
Then in your other projects, you can use this as a library/plugin. In project/plugins.sbt add:
addSbtPlugin("you" % "common-build" % "1.0")
And this will resolve your common build library when building your build.
If you need more information, look up more about sbt plugins and ignore the part about making something that extends a Plugin. Plugins are just libraries versioned with sbt's version number and your own. You should be able to put whatever code you want in there to share between builds.
Note: in 2016, Build.scala is deprecated for Build.sbt.
Here is the new (Dec. 2016) multi-module with App Scala sbt template by Michael Lewis.
Usage
sbt new lewismj/sbt-template.g8
You can then run:
sbt compile
sbt publish-local
sbt assembly
It is based on Scala SBT template (Library)
This giter8 template will write SBT build files for a Scala library.

Idiomatic way to write multi-project builds with .sbt files in sbt 0.13

I hear .sbt files have been improved in various ways in 0.13, and that now I can specify multi-project builds in them.
http://www.scala-sbt.org/0.13.0/docs/Community/ChangeSummary_0.13.0.html#sbt-format-enhancements mentions that we can now define subprojects in a .sbt file. I also know that multiple .sbt files in the root will be aggregated into a single conceptual file.
What I'd really like, though, is to not pollute my root with a dozen subproject .sbt files. Is there a way I can throw the subproject build.sbt files into their respective subdirectories, keep some common code between them somewhere shared, and then have a root build.sbt for the entire project that aggregates the subprojects? I have a similar setup in .scala files right now but would prefer to use .sbt files if possible.
If that isn't possible, what is the "correct" way to construct large multi-project builds with .sbt files?
It should already be the case in 0.12 that you can put .sbt files in the base directory of a subproject and the settings there will be included in that project's scope.
Code is reused between .sbt files by creating a normal .scala file in project/. The code in project/ will be available for use in the .sbt files. The definitions in one .sbt are not visible to other .sbt files, at least in 0.13. This is mainly an implementation restriction and it is undetermined whether this will be lifted in future versions.
The default root project will aggregate all subprojects, including those coming from projects defined in subProject/build.sbt.
The current difficulty is making it explicit.
For example, the following build.sbt in the root directory would define a subproject in sub/.
This is a full definition, defining the ID, base directory, etc... for the project.
<root>/build.sbt
lazy val sub = project
However, it cannot reference anything defined in <sub>/build.sbt. (The existence of sub/build.sbt is not known until after <root>/build.sbt is compiled and evaluated.)
So, to explicitly define what sub aggregates, you'd need something like:
sub/build.sbt
lazy val sub = project.in(file(".")).aggregates(subSub)
//or: lazy val sub = project in file(".") aggregate subSub
lazy val subSub = project
However, this duplicates the definition of sub.
A possible solution going forward is to make the root definition just a reference, like:
<root>/build.sbt
lazy val sub = LocalProject("sub")