-
Notifications
You must be signed in to change notification settings - Fork 344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update sbt Getting Started guide #306
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a great start! Good to see .value
demystified a bit
@@ -48,15 +48,9 @@ | |||
</div> | |||
</div> | |||
|
|||
<div clsas="arg_all"> | |||
<div clsas="arc_all"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HTML typo here, although it was already there so maybe it's a feature :)
scalaVersion := "$example_scala_version$" | ||
import Dependencies._ | ||
|
||
lazy val root = (project in file(".")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this build.sbt be simplified a bit, as it's the very first one we encounter? e.g.
- no need to define a project, just write all the settings at the top level
- no need to use inThisBuild
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recommendation for the new learners is to learn multi-project build.sbt and use that at all times.
The use of inThisBuild
is also intentional, in an "immersive learning" way. Basically, if someone copy-paste from the example (and I assume many would), I want it to be a good build from the first lesson, and that's why I'm switching to use sbt new
here. I can definitely add a note saying that the reader can skim over the details of the build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, could we add some rootProject
helper to simplify the project in file(".")
? This is something that will be in every build definition in the world going forward so it makes sense to keep it as tidy as possible.
Also, does it really need to be lazy
, or could we do without that?
It's also not clear to me what inThisBuild
is... does that apply to every project and not just the root project? Can I add it to any subproject and have the same effect? Would it make sense to put these in a separate val commonSettings = ...
right off that bat, since that's the style I seem to see everywhere else?
(These are not rhetorical questions, I really don't know)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, could we add some
rootProject
helper to simplify the project in file(".")?
I think it's possible, but I prefer reducing the number of constructs so it's fewer things to learn, especially if it doesn't add much functionality.
Also, does it really need to be
lazy
, or could we do without that?
lazy
does matter because subprojects can refer to each other via aggregate(...)
, dependsOn(...)
, and via scoping. You may or may not need it, but "always put lazy
" is easier than "well it depends.."
It's also not clear to me what inThisBuild is... does that apply to every project and not just the root project? Can I add it to any subproject and have the same effect?
inThisBuild
puts the setting in the build scope, which applies to all subprojects in the build.
Yes, you can add thisBuild
-scoped settings to any subproject.
Would it make sense to put these in a separate
val commonSettings = ...
?
commonSettings
is in fact the way that's currently documented, but it's repeating the same settings scoped to each subproject. thisBuild
-scoped has a few advantages:
- Because the value is centralized to a setting, it makes it easier to programmatically change it. This is useful for
version
(e.g. sbt-release usesversion in ThisBuild
) - A newly created subproject will be correct by default without manually adding in the common settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, could we add some rootProject helper to simplify the project in file(".")?
I think it's possible, but I prefer reducing the number of constructs so it's fewer things to learn, especially if it doesn't add much functionality.
using file(".")
has always looked a bit odd to me; I understand what it means, but in other contexts "." as CWD is usually assumed when omitted. But I understand that SBT's usual default is to use the val identifier as the dir name (another random thought, a bit OT: is it really worth that bit of magic to save a few extra chars? what if did away with it and just use Project
?)
random suggestion: provide in scope val currentDir = file(".")
so you can write lazy val root = project in currentDir
(and, possibly, even lazy val subp = project in currentDir / "subp"
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to start using that, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw the settingKey, taskKey, and inputKey macros exist also for the same reason. So you might want to start using those too:
val foo = SettingKey[Int]("foo", "I do foo")
val bar = TaskKey[Int]("bar", "I do bar")
val baz = InputKey[Int]("baz", "I do baz")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still advocate for making this explicit; I'd see no harm in typing "foo" a few times; in particular, (if it's not already) one could make Project("foo") == Project("foo", new java.io.File("foo"))
so you'd just end up writing val foo = Project("foo")
more on this theme on this gist and on sbt-dev once my post gets approved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm more of a DRY and conventions-over-configuration advocate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that; if other people are interested we can discuss this further on sbt-dev
:-)
|
||
```scala | ||
// The scalacOptions task may be defined in terms of the checksums setting | ||
scalacOptions := { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth pointing out that this can also be written more simply as scalacOptions := checksums.value
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I intentionally split the task dependencies from the task body, but I should add a section to explain that they are often combined in the wild.
```scala | ||
val scalacOptions = TaskKey[Seq[String]]("scalac-options", "Options for the Scala compiler.") | ||
val update = TaskKey[UpdateReport]("update", "Resolves and optionally retrieves dependencies, producing a report.") | ||
val clean = TaskKey[Unit]("clean", "Deletes files produced by the build, such as generated sources, compiled classes, and task caches.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should use the macro
The ID is used to refer to the project at the command line. The base | ||
directory may be changed from the default using the in method. For | ||
example, the following is a more explicit way to write the previous | ||
example: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 4c358f0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
|
||
As explained above, `.value` is a special method that is used to express | ||
the dependency to other tasks and settings. | ||
To emphasize the fact that it is *not* a normal method call that are evaluated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You switch from singular "it" to plural "are" in this sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't agree verb numbers to save my life. I hope a55fc0d looks ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's perfect.
|
||
This page explains `build.sbt` definition in more detail. | ||
It assumes you've read [.sbt build definition][Basic-Def] and | ||
[scopes][Scopes]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like where you are going with this revised guide. I am wondering whether the Scopes section is a real requirement in order to understand the following. Would it make sense to ignore the Scope axis, then delve into those details later ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably possible to push the scope page a few pages back, but at some point the reader would need to read it to grasp build.sbt fully.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if make
can be described as a singly-scoped SBT, and in turn, fully-scoped SBT as merely a "depth dimension" of makefiles?
If things could be inexactly considered in those terms, the novice could put their mind at rest not to worry about scopes until later, but at least be mentally prepared for when they were presented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @briantopping, but I'm not sure how/where would you say that so that it satisfies the "Makefile-literate" without sounding too arcane to everybody else.
There are several motivation to organizing the build this way. | ||
|
||
First is de-duplication. With flow-based programming, a task is executed only once even when it is depended by multiple tasks. | ||
For example, even when multiple tasks along the task graph depend on `compile in Compile`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, this is the only reference to a scope in the entire chapter. For the sake of the comparison with Make (which AFAIK does not have one such distinction, but I'm no expert) I wonder if it would make sense to avoid talking about scopes here, and introduce them later, as an addendum.
I posted the following on Gitter, but I see the conversation is happening here. I think it agrees with what @evacchi said above:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a lot of significant improvements in clarity. 👍
) | ||
``` | ||
|
||
**Note**: Don't worry if the above build file doesn't make sense to you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry about this language a bit, might it be better to say something like:
Don't worry if the details of this build file aren't clear yet, we'll come back to them later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be useful to include the link to the basic def page there as well?
For people who are not approaching this as a tutorial, some sort of text like the following might be useful (though I'm not sure whether its best to have this here, where we have a basic build.sbt, or later in Basic-Def where we talk about the structure):
If you are trying to make sense of an existing build.sbt which does not look like this, refer also to Bare-Def and Full-Def
In the past I've tried to bootstrap from existing examples, and where the tutorials differ its always unclear why and which way I should proceed.
@@ -241,12 +200,11 @@ task key still creates a value of type `T` when the task executes. | |||
|
|||
The `T` vs. `Task[T]` type difference has this implication: a setting can't | |||
depend on a task, because a setting is evaluated only once on project | |||
load and is not re-run. More on this in | |||
[more kinds of setting][More-About-Settings], coming up soon. | |||
load and is not re-run. More on this in [task graph][Task-Graph], coming up soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be tempted to drop ", coming up soon"
#### Benefits of hybrid flow-based programming | ||
|
||
Rather than thinking `settings` as a key-value map, | ||
a better analogy would be to think of it as a DAG (directed acyclic graph) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the convention of introducing an acronym, by using the full name, followed by the acronym in brackets, rather than vice versa. I think it's less off putting if you're not familiar with the acronym.
} | ||
``` | ||
|
||
**Note**: The values calculated above are nonsensical for `scalaOptions`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like a really weird thing to do. At least for me, on seeing an example like this, my instinct is to try and understand what it's trying to achieve, which would be pointless in this case. It would be a while before I noticed this note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can move the Note above the example if it helps.
``` | ||
|
||
#### Common settings | ||
#### Build settings | ||
|
||
To factor out common settings across multiple projects, | ||
create a sequence named `commonSettings` and call `settings` method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to line up with the title/example any more. Also, I wonder if using a term like 'Build-wide settings' might help to make the distinction clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a lot of good stuff, thanks for your work on this.
See instruction to install manually. | ||
``` | ||
\$ port install sbt | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think pretty much everyone I know uses sbt-extras … it would be nice to at least mention it or even recommend it directly. Personally I think it should be the one and only launcher, although it may not work on Windows. dunno.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As of October it does work on Windows (Cygwin): dwijnand/sbt-extras#167
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. The launcher story is a mess on its own so I've split it up to sbt/sbt#2889
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that the vanilla launcher supports .jvmopts
I've stopped using sbt-extras.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@milessabin In my experience, sbt-extras is considerably more reliable at dealing with a number of cases than the vanilla launcher, including sbt version. I've also seen occasional really bizarre dependency symptoms in someone else's build that were fixed by swapping from the vanilla launcher to sbt-extras. It's still the standard, in my book.
|
||
Hello, World | ||
------------ | ||
|
||
This page assumes you've [installed sbt][Setup]. | ||
|
||
### Create a project directory with source code | ||
### sbt new command |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to simply say
This page assumes you've installed sbt 0.13.10 or later.
Since there are is no guidance given for creating a project by hand? This seems fine, although Edit: resolvedsbt new
does not work for me and I have a hard time believing I'm the only person with this problem. We'll find out I guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to put the disclaimer. There's also a project that we're working on that turns select Giter8 template into a download, so we can try that too.
) | ||
``` | ||
|
||
**Note**: Don't worry if the above build file doesn't make sense to you. | ||
In [.sbt build definition][Basic-Def] you'll learn more about how to write | ||
a `build.sbt` file. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the Setting the sbt version section below might be removed or re-worded … the g8 template sets this for you, and I don't think there's any reason to mention that it's optional (I would prefer that it not be).
@@ -50,6 +46,10 @@ src/ | |||
Other directories in `src/` will be ignored. Additionally, all hidden | |||
directories will be ignored. | |||
|
|||
Source code can be placed in the project's base directory as | |||
`hello/app.scala`. However, most people don't do this for real projects; | |||
too much clutter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason to mention this here? As you say, it's not a good practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have to for the sake of completeness. This is a page titled Directories, and it talks about the places where sbt will look for *.scala
file out of the box. Since sbt is coded to look for them the base directory, we should mention it.
Another reason to mention this is because it does become relevant/recommended for the meta-build. See http://www.scala-sbt.org/0.13/docs/Organizing-Build.html#sbt+is+recursive
@@ -61,7 +61,7 @@ form the complete build definition. See [organizing the build][Organizing-Build] | |||
``` | |||
build.sbt | |||
project/ | |||
Build.scala | |||
Dependencies.scala |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above it says
project
can contain.scala
files, which are combined with.sbt
files
which I find absolutely baffling since .sbt
files are not Scala files and .scala
source files don't compose. Do we really need to mention this in the Getting Started guide?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can link to http://www.scala-sbt.org/0.13/docs/Organizing-Build.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd vote for not talking about the project/Foo.scala
format until later in the Organizing-Build
section. After all, it's purely an organizational/neatness thing, and isn't necessary to learn so early
@@ -110,7 +69,7 @@ rather than complete Scala statements. | |||
`build.sbt` may also be | |||
interspersed with `val`s, `lazy val`s, and `def`s. Top-level `object`s and | |||
`class`es are not allowed in `build.sbt`. Those should go in the `project/` | |||
directory as full Scala source files. | |||
directory as Scala source files. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are definitions in in the .scala
made visible in the .sbt
file? We need an example here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also a link to http://www.scala-sbt.org/0.13/docs/Organizing-Build.html here as well.
@@ -138,8 +97,8 @@ the value `"hello"`. | |||
If you use the wrong value type, the build definition will not compile: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above the text says
In this case, the returned
Setting[String]
is a transformation to add or replace thename
key in sbt's map, giving it the value"hello"
.
But we just said the map is immutable. Can we maybe not claim it's immutable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I also find the immutability break dancing to be odd. I'll remove the mention of it.
To be fair, the original authors might be saying that even though the DSL exposes procedural interface (assigning to a variable-like thing), internally it's kept purely functional by translating the DSL to something like:
scala> Map.empty[Symbol, Int].updated('x, 1).updated('x, 2)
res0: scala.collection.immutable.Map[Symbol,Int] = Map('x -> 2)
I don't see how that can be relevant to sbt users.
lazy val root = (project in file(".")). | ||
settings( | ||
lazy val root = (project in file(".")) | ||
.settings( | ||
name := 42 // will not compile | ||
) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Below it says
val
s anddef
s must be separated from settings by blank lines.
(a) is this true? if so there needs to be section explaining the blank line thing. And (b) we're not showing settings at the top level (they're all in .settings(...)
) so this may be confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it's no longer true since 0.13.7, so we can replace it with a warning for ppl to upgrade to a recent version at the top of the page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is my first pass reviewing this; may have more feedback later when I have more time to take a look.
Thanks for the cleanup!
scalaVersion := "$example_scala_version$" | ||
import Dependencies._ | ||
|
||
lazy val root = (project in file(".")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, could we add some rootProject
helper to simplify the project in file(".")
? This is something that will be in every build definition in the world going forward so it makes sense to keep it as tidy as possible.
Also, does it really need to be lazy
, or could we do without that?
It's also not clear to me what inThisBuild
is... does that apply to every project and not just the root project? Can I add it to any subproject and have the same effect? Would it make sense to put these in a separate val commonSettings = ...
right off that bat, since that's the style I seem to see everywhere else?
(These are not rhetorical questions, I really don't know)
version := "0.1.0-SNAPSHOT" | ||
)), | ||
name := "Hello", | ||
libraryDependencies += scalaTest % Test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first time I'm seeing this % Test
syntax; is that the new standard way of doing it that I should be following?
Where does scalaTest
come from? I've never seen that either, and feel it might be clearer if we generated the full "org.scalatest" %% "scalatest" % "..."
since a reader would be able to guess that that's some kind of module coordinates, vs scalaTest
being an opaque identifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the notation was there, but got documented in #15. I like it since it feels more typesafe.
scalaTest
comes from import Dependencies._
. The idea of putting module ids in a file is discussed here - http://www.scala-sbt.org/0.13/docs/Organizing-Build.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'd prefer % Test
over % "test"
, but I just hadn't ever seen it before. If that's the way to do things now, then happy to standardize on it.
I feel like putting things in a separate project/Dependencies.scala
file, so early in the "Getting Started" section, is a bit confusing. Remember, at this point we know nothing about SBT at all. I think dealing with build.sbt
is quite enough to learn, and anyway seeing/familiarizing with the "org.scalatest" %% "scalatest" % "..."
syntax is itself a valuable thing to do.
I think we should only break things out into separate files when the build gets a bit bigger, messier and non-trivial, which could be documented but somewhere later than the first Getting Started section
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the only problem I see with Test
(which I prefer over "test"
) is that apparently you have to resort to strings as soon as you have multiple specifiers (e.g. "it,test"
see the manual)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be possible to add some implicits which allow multiple specifiers, even with this syntax. For example: scalaTest % (Test, Integration)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that would work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's split the discussion on enhancing % Test
DSL to this issue - sbt/sbt#1441
@@ -50,6 +46,10 @@ src/ | |||
Other directories in `src/` will be ignored. Additionally, all hidden | |||
directories will be ignored. | |||
|
|||
Source code can be placed in the project's base directory as | |||
`hello/app.scala`. However, most people don't do this for real projects; | |||
too much clutter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we could phrase this as
+Source code can be placed in the project's base directory as
+`hello/app.scala`, which may be useful for small one-file throw-away
+projects, though for larger projects people tend to keep source files in the
+relevant src/main/XXX folder to keep things neat
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...or as @tpolecat we could also just not talk about it
@@ -53,21 +53,23 @@ quotes. For example, | |||
In this example, `testOnly` has arguments, `TestA` and `TestB`. The commands | |||
will be run in sequence (`clean`, `compile`, then `testOnly`). | |||
|
|||
**Note**: Running in batch mode requires JVM spinup and JIT each time, so your build may run slower. For day-to-day coding, we recommend using the sbt shell. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add "or to use the Continuous build and test feature below, which works from the external command-line and also keeps the JVM running to allows fast re-builds"
|
||
This page describes sbt build definitions, including some "theory" and | ||
the syntax of `build.sbt`. It assumes you know how to [use sbt][Running] | ||
and have read the previous pages in the Getting Started Guide. | ||
|
||
### Three Flavors of Build Definition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎆
name := "hello", | ||
organization := "com.example", | ||
scalaVersion := "$example_scala_version$", | ||
version := "0.1.0-SNAPSHOT" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we are going to teach people inThisBuild
in the first example, we should continue with that in all further examples. If we think it's too complicated to explain, we should strip it out of the first example.
So far it seems to me able half-half with and without inThisBuild
; my personal vote would be to strip it out, since I've written tons of SBT configs and haven't ever used it, but I don't mind as long as it's consistent throughout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inThisBuild()
came in sbt 0.13.9, and hasn't been documented much.
I have to mull this over on whether to include this in all examples or not. Maybe we can wait till we discuss multi-projects.. /cc @dwijnand
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still believe one should use ThisBuild scope as much as possible, over the commonSettings approach (for one, the state builds faster as there's less to compute). Sadly, for implementation details, you often have to fallback to commonSettings for some things (eg. anything depending on baseDirectory, including transitively dependant).
As such, I would introduce inThisBuild, and fairly early, as I agree to a more complete example that can be stripped down, instead of a minimal one that has to be built up.
Though I would not put it nested in the .settings of the root project. Firstly, it's more nesting than necessary. Secondly it makes it look like there's a specific reason why it's there, which isn't true. (I'm my fantasy sbt, settings not nested in a project are automatically scoped in ThisBuild, but alas..)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a difference between putting inThisBuild
inside the root
project vs. defining it at the top of build.sbt
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. And that's my point. ("Which isn't true" above)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's split up the discussion around inThisBuild(...)
to #307
### What's the point of the build.sbt DSL? | ||
|
||
As we saw before, a [build definition][Basic-Def] consists of subprojects | ||
with a map called `settings` describing the subproject. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"with some settings
describing the subproject" would be clearer I think. The fact that it's a map is not very useful, especially since it's not a scala.Map
, nor is it any "normal" map that someone may think of since it contains tons of dependency information and other things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea. The narrative of "build definition" is
Each project is associated with an immutable map (set of key-value pairs) describing the project.
so I was trying to build some continuity with it. I can go with "with some settings describing the subproject"
What the `Setting` sequence encodes is tasks and the dependencies among them, | ||
similar to [Make][Make] (1976), [Ant][Ant] (2000), and [Rake][Rake] (2003). | ||
|
||
#### Intro to Make |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be kicked down to the tail end of the file? "How to use" SBT by declaring dependencies and inspecting stuff should be first priority. Justifying our own design decisions, and a comparative analysis with other build tools, should come later. Perhaps even on a separate page.
As a newbies, I do not want a tour of 3 other build tools before figuring out how to use SBT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm betting that people need to be informed about philosophical background of build.sbt
DSL to write build.sbt
.
I'm fine with pushing the section down to the bottom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the information is useful and interesting (or at least I find it that way). But I also think it should be pushed to down or later in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that explaining how SBT works should be the first priority. sbt may be the first build tool the reader encounters, and the current layout might give the impression that you need experience with other build tools to understand sbt. That's not true at all! Comparison with other build tools could belong to a separate "Coming from Make/Rake/..." section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My feeling is that people can get by quite far without the philosophical background. Things like
- Assigning some settings to literals
- Adding a
scala-reflect
libraryDependency
that depends on yourscalaVersion
- Adding
unmanagedSourceDirectories
s based on yourscalaVersion
can all be accomplished by cargo-culting, without needing much philosophy at all, and will probably get a new SBT user quite far.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ordering is now flipped in 45aaa78.
while sbt and plugins can provide various features such as compilation and | ||
library dependency management as functions that can be reused. | ||
|
||
### Declaring dependency to other tasks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be the top of this file IMO
This sets the name in terms of its previous value as well as the | ||
organization and version settings. | ||
|
||
### Inlining .value calls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these should come first, with the {}
version coming after.
In particular, all the {}
examples above are so trivial they should be inline. Artificially splitting them into two statements with no obvious necessity will just confuse people, who will think it's a convention/best-practice/requirement.
We should pick a better, more motivating example to make the {}
example make sense. Maybe add a println
in the middle of one to help debug/show stuff? Or have as sufficiently large example (e.g. the string interpolated one below) that breaking out intermediate val
s makes sense for clarity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be useful to demonstrate specifically that you can't prevent a task from being evaluated by guarding with an if
statement. This behavior is totally jaw-dropping and it's important to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{}
version is the cognitive training wheel to signal to the reader that .value
is really not a normal Scala method call. There's a temporal line on the sand between val x = ...
and ur...
scalacOptions := {
val ur = update.value // update task happens-before scalacOptions
val x = clean.value // clean task happens-before scalacOptions
// ---- begin scalacOptions ----
ur.allConfigurations.take(3)
}
I can add the println
demo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And if
demo too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'm not convinced that it's a good training wheel. I think one example with this artificial split - the one you give here, with all the comments - is sufficient to illustrate the point. The comments are what make it understandable at all: otherwise it's just confusing to see these trivial assignments that "appear" to do nothing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like your "temporal line in the sand" example. You somewhat talk around this here and in the section in Custom-Settings where you show the execution order of the .value
calls. Explicitly putting something like your temporal explanation here would be useful.
[Make]: https://en.wikipedia.org/wiki/Make_(software) | ||
[Ant]: http://ant.apache.org/ | ||
[Rake]: https://ruby.github.io/rake/ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section should start off defining the key terms:
- Task: can depend on both settings and tasks
- Setting: something that can be set early, can depend on settings but cannot depend on tasks
- Key: an identifier that is associated with a task or setting
With one/two-liner descriptions. The details of setting dependencies, syntax, etc. can come later, but a paragraph that makes it clear "there are keys and tasks and settings and they are similar but different things" would I think help greatly for anyone trying to understand subsequent exposition
Thank you all for the feedback. It's really heartwarming to see people help out where possible. |
- `+=` will append a single element to the sequence. | ||
- `++=` will concatenate another sequence. | ||
|
||
For example, the key `sourceDirectories in Compile` has a `Seq[File]` as its |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is sourceDirectories in Compile
a key?
Or is sourceDirectories in Compile
a task and sourceDirectories
the key?
If sourceDirectories in Compile
is a task, is the un-scoped sourceDirectories
also a task?
This is not clear to me, even given this description, as someone who has used SBT for years. We need to be more precise and consistent with this terminology so we can drill into people the same set of standard terms
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we discuss the build.sbt DSL, the nomenclature could be something like:
organization := { name.value }
----------- -- -------------
key operator body
--------------------------------
setting (or task) expression
But colloquially we often say "organization is a setting."
In this case, sourceDirectories in Compile
is a scoped key of type SettingKey[Seq[File]]
.
The page on scopes is still really infuriating. Should I post comments here or open another issue? |
I too think Task Graph would be helpful in setting the context, but I think it would be useful to describe sbt's shell environment usage first like |
Would it be possible to create two different documentation "parts"? One of the things I find refreshing about the changes you have here it is almost devoid of forward references. I think that's hurt a lot of the existing docs. Forward references and tight sequencing allow docs to be concise, but beginners need to map from familiar paradigms. It can be really hard to do that if a single concept gets missed. Splitting the docs into an "inexact, easily mappable colloquial" part and a "concise cross-referenced (but still readable)" part is valuable because different people learn differently. People that learn easily from one style may be find the other style to be unusable, at least early on. Both need an entry to get to a basic conversational understanding of SBT, then they can both work from a single document (the concise reference). I also think this might open the door for additional documentation contributors. The current docs are tightly sequenced and there is a "wrong way" to present things, potentially excluding writers that may have a less exact presentation. This has it's own pitfalls, but I don't think it's worth dismissing for the reasons in the previous paragraph. |
to use sbt $app_version$. | ||
Without this file sbt will chose the sbt version installed on the machine, | ||
but setting the sbt version in | ||
`project/build.properties` avoids any potential confusion. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The transition from the follow-along style of the previous chapter is slightly jarring here. Maybe something like this?
Specifying the required sbt version
As part of your build definition you will specify the version of sbt that your project requires. This allows people with different versions of the
sbt
launcher to build the same project with consistent results. To do this, your project should have a file namedproject/build.properties
that specifies the required version as follows:sbt.version=$app_version$
If the required version is not available locally, the
sbt
launcher will download it for you. If this file is not present, thesbt
launcher will choose an arbitrary version, which is discouraged because it makes your project non-portable.
I couldn't address all of the comments, but got to most of them on the new contents, so I'd like to merge this PR before the work week begins tomorrow. Huge thanks to all of you who took time from the off time, read through the docs, and gave feedback! The result came out improved with all your suggestions and discussions. If you have more ideas about either the documentation or the sbt itself, please post to https://groups.google.com/forum/#!forum/sbt-dev or send us a PR. |
Great work on this @eed3si9n, thank you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this, @eed3si9n!
``` | ||
|
||
Each project is associated with an immutable map (set of key-value pairs) describing the project. | ||
Each subproject is associated with a sequence of key-value pairs describing the subproject. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random wordsmithing suggestion:
Each subproject is configured by key-value pairs.
Each `Setting` is defined with a Scala expression. The expressions in | ||
`settings` are independent of one another, and they are expressions, | ||
rather than complete Scala statements. | ||
Let's take a closer look at the build.sbt DSL: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Most places "build.sbt" gets the backtick treatment, i.e. build.sbt
.
transformation to add or replace the `name` key in sbt's map, giving it | ||
the value `"hello"`. | ||
On the left-hand side, `name`, `version`, and `scalaVersion` are *keys*. | ||
A key is an instance of `SettingKey[T]`, `TaskKey[T]`, or `InputKey[T]` where `T` is the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth linking these classes to their scaladoc, as was done above for Project
at https://github.com/sbt/website/pull/306/files#diff-a9fd40e23fcbc0dd7b2dba0646eb8f83R43 ?
|
||
```scala | ||
lazy val root = (project in file(".")) | ||
.settings( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this document somewhere explain that a setting/task expression at the top level of the *.sbt
file is equivalent to an expression inside of settings(...)
. If not, should it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a page called Appendix: Bare .sbt build definition, but I'm steering new users away from using that in build.sbt.
name := "Hello", | ||
organization := "com.example", | ||
scalaVersion := "$example_scala_version$", | ||
version := "0.1.0-SNAPSHOT", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: It seems like the :=
s should either be properly aligned within this .settings()
block or the leading whitespace on this line should die.
.settings( | ||
inThisBuild(List( | ||
// Same as: | ||
// organization in ThisBuild := "com.example" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that to say that in ThisBuild
would be applied to all the settings in the List
? if so I think it's worth trying to hint at that a little more explicitly.
|
||
#### Tasks based on other keys' values | ||
|
||
You can compute values of some tasks or settings to define or append value for another task. It's done by using `Def.task` and `taskValue`, as argument to `:=`, `+=` or `++=`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either "as an argument" or "as agruments"
|
||
#### Tasks based on other keys' values | ||
|
||
You can compute values of some tasks or settings to define or append value for another task. It's done by using `Def.task` and `taskValue`, as argument to `:=`, `+=` or `++=`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need an article in "or append a value"
|
||
#### Tasks based on other keys' values | ||
|
||
You can compute values of some tasks or settings to define or append value for another task. It's done by using `Def.task` and `taskValue`, as argument to `:=`, `+=` or `++=`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comma before "as argument" is unneeded.
The name of the val is used as the subproject's ID, which | ||
is used to refer to the subproject at the sbt shell. | ||
|
||
Optionally the base directory may be omitted if it's same as the name of the val. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Missing article: "if it's the same"
@leifwickland Thanks for the proofing/copyediting. Much appreciated! |
`hello/app.scala`, which may be for small projects, | ||
though for normal projects people tend to keep the projects in | ||
the `src/main/` directory to keep things neat. | ||
The fact that you can place `*.scala` source code might seem like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be "The fact that you can place *.scala
source code in the projects the base directory"?
name := "Hello", | ||
publish := (), | ||
publishLocal := () | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is confusing to me because inThisBuild
is used within the .settings
of a project. Is it possible to write these global definitions outside of a project definition?
Also, it would be super interesting to mention inConfig
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, maybe you should also explain the difference between ThisBuild
and Global
?
settings( | ||
// other settings | ||
) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something that’s missing, IMHO: I would rather change the way we recommend scoping keys to several axes to this syntax: foo in task in config in project
rather than foo in (task, project, config)
because I never remember the right order of the axes in the latter version.
[scope][Scopes] that defines it. | ||
|
||
It's possible to create cycles, which is an error; sbt will tell you if | ||
you do this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move this section to the Scopes
page.
or flow-based programming, similar to `Makefile` and `Rakefile`. | ||
|
||
The key motivation for the flow-based programming is de-duplication, | ||
parallel processing, and customizability. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be interesting to also add a section explaining why it is possible to write:
foo := bar.value
But not:
val foo = bar.value
Though the following is possible:
val foo = Def.setting(bar.value)
In other words: what’s the magic happening in :=
that makes it possible to omit the surrounding Def.setting
. Maybe that could help understanding the Def.task(…).taskValue
trick, too…
This is a proposal to update the Getting Started guide a bit.
I've tried to remove obsolete information and things that mostly sbt core devs care about (over-emphasis on immutability and the return types of DSL expressions); and to update some terminology (sbt shell as opposed to "the interactive mode").
I've also added a new page that tries to explain WHY we need build.sbt not just how and what: https://github.com/sbt/website/blob/3b99844c8e975093c32f3c12417f4dbaaebe8b56/src/reference/00-Getting-Started/07A-Task-Graph.md
Let me know if I'm moving towards the right direction.
/cc @dwijnand, @pfn, @heathermiller, @tpolecat, @lihaoyi