Skip to content
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

Merged
merged 25 commits into from
Jan 3, 2017
Merged

Update sbt Getting Started guide #306

merged 25 commits into from
Jan 3, 2017

Conversation

eed3si9n
Copy link
Member

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

@eed3si9n eed3si9n changed the title Update Getting Started guide Update sbt Getting Started guide Dec 30, 2016
Copy link
Contributor

@cb372 cb372 left a 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">
Copy link
Contributor

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("."))
Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Contributor

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)

Copy link
Member Author

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:

  1. 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 uses version in ThisBuild)
  2. A newly created subproject will be correct by default without manually adding in the common settings.

Copy link

@evacchi evacchi Dec 31, 2016

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")

Copy link

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!

Copy link
Member

@dwijnand dwijnand Jan 1, 2017

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")

Copy link

@evacchi evacchi Jan 1, 2017

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

Copy link
Member

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.

Copy link

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 := {
Copy link
Contributor

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 ?

Copy link
Member Author

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.")
Copy link
Member

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:
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 4c358f0

Copy link
Member

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
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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].
Copy link

@evacchi evacchi Dec 30, 2016

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 ?

Copy link
Member Author

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.

Copy link

@briantopping briantopping Dec 31, 2016

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.

Copy link

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`,
Copy link

@evacchi evacchi Dec 30, 2016

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.

@briantopping
Copy link

I posted the following on Gitter, but I see the conversation is happening here. I think it agrees with what @evacchi said above:

One thought that comes to mind: What if What's the point of the build.sbt DSL? was moved closer to the first page of the docs, or even put on the first page? In other words, I think this section has a lot of value to set the context for “how to think” and things like scope build on this, not the other way around...

Copy link

@philwills philwills left a 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.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

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.

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)

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`,

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.

Copy link
Member Author

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

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.

Copy link

@tpolecat tpolecat left a 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
```

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.

Copy link
Member

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

Copy link
Member Author

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

Copy link

@milessabin milessabin Jan 1, 2017

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.

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
Copy link

@tpolecat tpolecat Dec 30, 2016

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 sbt 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. Edit: resolved

Copy link
Member Author

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.

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.

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.

Copy link
Member Author

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

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -138,8 +97,8 @@ the value `"hello"`.
If you use the wrong value type, the build definition will not compile:

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 the name 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?

Copy link
Member Author

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
)
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below it says

vals and defs 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.

Copy link
Member Author

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.

Copy link
Contributor

@lihaoyi lihaoyi left a 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("."))
Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Member Author

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

Copy link
Contributor

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

Copy link

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)

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)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that would work

Copy link
Member Author

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.
Copy link
Contributor

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

Copy link
Contributor

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.
Copy link
Contributor

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
Copy link
Contributor

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"
Copy link
Contributor

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

Copy link
Member Author

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

Copy link
Member

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..)

Copy link
Member

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?

Copy link
Member

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)

Copy link
Member Author

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.
Copy link
Contributor

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

Copy link
Member Author

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
Copy link
Contributor

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

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor

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 your scalaVersion
  • Adding unmanagedSourceDirectoriess based on your scalaVersion

can all be accomplished by cargo-culting, without needing much philosophy at all, and will probably get a new SBT user quite far.

Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Contributor

@lihaoyi lihaoyi Dec 30, 2016

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 vals makes sense for clarity

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.

Copy link
Member Author

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And if demo too.

Copy link
Contributor

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

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/

Copy link
Contributor

@lihaoyi lihaoyi Dec 30, 2016

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

@dwijnand
Copy link
Member

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
Copy link
Contributor

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

Copy link
Member Author

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]].

@tpolecat
Copy link

The page on scopes is still really infuriating. Should I post comments here or open another issue?

@eed3si9n
Copy link
Member Author

@briantopping

One thought that comes to mind: What if What's the point of the build.sbt DSL? was moved closer to the first page of the docs, or even put on the first page? In other words, I think this section has a lot of value to set the context for “how to think” and things like scope build on this, not the other way around...

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 run and test. The earliest I can move Task Graph would be before .sbt build definition page.

@briantopping
Copy link

@eed3si9n

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 run and test. The earliest I can move Task Graph would be before .sbt build definition page.

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.

Copy link

@tpolecat tpolecat Jan 3, 2017

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 named project/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, the sbt launcher will choose an arbitrary version, which is discouraged because it makes your project non-portable.

@eed3si9n
Copy link
Member Author

eed3si9n commented Jan 3, 2017

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.

@eed3si9n eed3si9n merged commit 722f7ec into master Jan 3, 2017
@eed3si9n eed3si9n deleted the wip/docs branch January 3, 2017 05:19
@tpolecat
Copy link

tpolecat commented Jan 3, 2017

Great work on this @eed3si9n, thank you.

Copy link
Contributor

@leifwickland leifwickland left a 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.
Copy link
Contributor

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:
Copy link
Contributor

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
Copy link
Contributor

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(
Copy link
Contributor

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?

Copy link
Member Author

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",
Copy link
Contributor

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"
Copy link
Contributor

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 `++=`.
Copy link
Contributor

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 `++=`.
Copy link
Contributor

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 `++=`.
Copy link
Contributor

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.
Copy link
Contributor

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"

@eed3si9n
Copy link
Member Author

eed3si9n commented Jan 3, 2017

@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
Copy link
Contributor

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 := ()
)
Copy link
Contributor

@julienrf julienrf Jan 9, 2017

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.

Copy link
Contributor

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
)
```
Copy link
Contributor

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.
Copy link
Contributor

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.
Copy link
Contributor

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…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.