ruach
ruach

Reputation: 1469

<scala> How the scope delegation works in SBT?

I've read a reference to understand how the scope delegation works in SBT.

From the above reference page, I excerpted an Exercise D.

ThisBuild / scalacOptions += "-Ywarn-unused-import"

lazy val projD = (project in file("d"))
  .settings(
    test := {
      println((Compile / console / scalacOptions).value)
    },
    console / scalacOptions -= "-Ywarn-unused-import",
    Compile / scalacOptions := scalacOptions.value // added by sbt
  )

What would you see if you ran projD/test?

List()

List (-Ywarn-unused-import)

something else?

And their reasoning was

The answer is List(-Ywarn-unused-import). Rule 2 finds projD / Compile / Zero, Rule 3 finds projD / Zero / console, and Rule 4 finds ThisBuild / Zero / Zero. Rule 1 selects projD / Compile / Zero because it has the subproject axis projD, and the configuration axis has higher precedence over the task axis.

So far so good, I could understand the reason why Compile/console/sclacOptions will be scoped to projD/Compile/zero/scalacOptions. It's because the configuration axis of projD/Compile/zero is more concrete than the projD/zero/console compared to the key that we want to know (Compile/console/scalacOptions).

Next, Compile / scalacOptions refers to scalacOptions.value, we next need to find a delegate for projD / Zero / Zero. Rule 4 finds ThisBuild / Zero / Zero and thus it resolves to List(-Ywarn-unused-import).

Here, I couldn't understand why ThisBuild / Zero/ Zero wins the projD/ Compile/ Zero. Because the scope of the key that we want to find is projD/ zero/ zero, so projD/Compile/zero has more specific value compared to the Thisbuild [Following the Rule 1 in the reference].

I think the reason is Compile/ scalacOptions has the value of scalacOptions.value, which generates recursive definition. Then, we could have used the projD/ Zero/ console.

Upvotes: 2

Views: 194

Answers (1)

laughedelic
laughedelic

Reputation: 6460

I think you're just a bit confused about the references to the difference scopes in the second part of the explanation. Let's write down all scopes in the example explicitly:

ThisBuild / Zero / Zero / scalacOptions += "-Ywarn-unused-import"

lazy val projD = (project in file("d"))
  .settings(
    test := { println((Compile / console / scalacOptions).value) }
  )

projD / Zero / console / scalacOptions -= "-Ywarn-unused-import"
projD / Compile / Zero / scalacOptions := (projD / Zero / Zero / scalacOptions).value

I had to take out these two settings, to be able to refer to projD, but otherwise it's the same definition.

So from the first part you understood that projD / Compile / Zero / scalacOptions wins over projD / Zero / console / scalacOptions and ThisBuild / ....

Now let's just take its value and use in our task. But what is its value? It refers to projD / Zero / Zero / scalacOptions which is not defined explicitly. So we need to find a delegate for it. At this moment we're already not competing with projD / Compile and projD / console and the only fitting key in scope is in the scope ThisBuild / Zero / Zero.

I tried to illustrate it here (ommitting the / scalacOptions to save space):

projD     / Compile / console // the one we need in the task
projD     / Zero    / console // could be applied by rule 3, but looses to the next one:
projD     / Compile / Zero    // applies by rule 2 (task scope delegation), defined with next one:
projD     / Zero    / Zero    // not defined explicitly, so is delegated:
ThisBuild / Zero    / Zero    // applies by rule 4 (project scope delegation)

Upvotes: 2

Related Questions