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

Document usage of ThisBuild over Global #3652

Open
jvican opened this issue Oct 20, 2017 · 10 comments
Open

Document usage of ThisBuild over Global #3652

jvican opened this issue Oct 20, 2017 · 10 comments

Comments

@jvican
Copy link
Member

jvican commented Oct 20, 2017

(edited by @dwijnand, previous title: "Add inGlobal to allow scoping settings globally")

problem

inThisBuild is a standard operation in any build.sbt and sbt plugin. However, there's no counterpart for scoping in Global.

expectation

I would like to have an inGlobal operation to scope there important keys that are true at the global level (organization, license, et cetera).

This would be the implementation of inGlobal, placed in Project.scala:

  def inGlobal(ss: Seq[Setting[_]]): Seq[Setting[_]] =
    inScope(Scope.GlobalScope)(ss)

notes

I would like to add this to the Scala Center spree that we're having at Lambda World next week.

sbt version: 0.13.x and 1.x

@Duhemm Duhemm added the Spree label Oct 20, 2017
@dwijnand
Copy link
Member

The difference between Global and build-level is relevant when you have more than one build. In that case you see that what you refer to as "true at the global level (organization, license, et cetera)" isn't (always) true - any build you add could easily have a different organization, license, et cetera.

The way I see it is Global scope should be used only for:

  • defining the default value of new keys
  • for build-agnostic keys, for instance things that configuration the behaviour of the task graph or commands processing engine. Like cancellable.

Therefore I think the absence of inGlobal is a good thing, to avoid misuse.

@jvican
Copy link
Member Author

jvican commented Oct 20, 2017

The way I see it is Global scope should be used only for: defining the default value of new keys and for build-agnostic keys, for instance things that configuration the behaviour of the task graph or commands processing engine. Like cancellable.

I agree with this. I'm happy to close this ticket so long as this is explained in the sbt docs and people know that they should scope in ThisBuild always in build.sbt.

@dwijnand
Copy link
Member

I agree. We'll use this issue to track that documentation improvement.

@dwijnand dwijnand changed the title Add inGlobal to allow scoping settings globally Document usage of ThisBuild over Global May 14, 2018
@eed3si9n eed3si9n added the uncategorized Used for Waffle integration label Sep 18, 2018
@eed3si9n
Copy link
Member

eed3si9n commented Oct 1, 2019

I don't see how a doc on this topic can be a contribution. It needs to be written by someone with a good understanding of scoping.

@mdedetrich
Copy link
Contributor

mdedetrich commented Feb 12, 2023

At the risk of necroing this issue, I have found myself writing various sbt plugins and I wan't to make sure that I am fully clear on what the distinction between Global and ThisBuild is. As far as I understand, if you have keys/commands that only make sense globally, they should be in Global. Good examples are keys like concurrentRestrictions, this only makes sense globally because its dictating how many concurrent tests you run in sbt.

So one part I find slightly confusing is this statement

The difference between Global and build-level is relevant when you have more than one build. In that case you see that what you refer to as "true at the global level (organization, license, et cetera)" isn't (always) true - any build you add could easily have a different organization, license, et cetera.

Can you clarify what you mean when you say "more than build". Are you talking about the typical case of having multiple sbt projects in a single sbt build with each project having its own name, generating its own artifact etc etc or is the some strict terminology definition of "build" that I am unaware of? Or by build, do you literally mean different arbitrary sbt builds (i.e. different directories having their own build.sbt).

For context I am doing a change to this SBT plugin (see pjfanning/sbt-source-dist#15) and I wan't to make sure that I am actually correct in my understanding of Global.

@eed3si9n
Copy link
Member

Or by build, do you literally mean different arbitrary sbt builds (i.e. different directories having their own build.sbt).

It's not a well-documented feature, but an sbt build (directory with build.sbt) can reference another build (another directory with build.sbt, or typically a GitHub repo) via ProjectRef(...). I created sbt-sriracha, which indirectly documents the status quo - https://eed3si9n.com/hot-source-dependencies-using-sbt-sriracha/.

In addition to that, there's sort of a cultural/conventional differences between the two. Set aside config-scoping and task-scoping, the strength of scoping is:

subproject-scoping > ThisBuild-scoping > Global-scoping

So what we normally recommend for plugin authors to scope is use Global to provide the default values of original uninitialized keys, and consume it at subproject-scoping, and avoid ThisBuild. This way, if plugin consumers wants to override the default value they could do so using ThisBuild-scoping because it sits in between subproject-scoping and Global-scoping.

However, plugin authors should be careful at mutating the Global keys provided by sbt or other plugins, because essentially the values are going to be plugin-load-order-dependent, which is annoying.

@mdedetrich
Copy link
Contributor

mdedetrich commented Feb 13, 2023

Many thanks for the detailed reply!

So what we normally recommend for plugin authors to scope is use Global to provide the default values of original uninitialized keys, and consume it at subproject-scoping, and avoid ThisBuild.

Is this the case even for keys which have a high chance of being set for plugin consumers, i.e. in this PR pjfanning/sbt-source-dist#15 I am trying to fix things by putting the keys in global scope (as you state) however it means that when a plugin consumer wants to set the key they need to use Global / (see test at https://github.com/pjfanning/sbt-source-dist/pull/15/files#diff-dc2b139bce9694f985ac990bffc0f317844d9757e27bdcce15c64635028ce45eR3) which I find unusual because I don't typically ever need to set keys in the Global scope (I would expect to use either ThisBuild / or just no scope at all). Or in this case should the plugin author set the keys both in globalSettings and projectSettings?

Also on another point when it comes to plugin authors mutating the values of keys created by other sbt plugin/s (assuming the plugin author has those sbt plugins specified in requires) should this mutation be done using projectSettings,buildSettings or globalSettings (an example is something like https://github.com/mdedetrich/sbt-apache-sonatype/blob/main/src/main/scala/org/mdedetrich/apache/sonatype/SonatypeApachePlugin.scala#L69-L70) or should we mirror what the plugin author used (i.e. if the plugin author put keys into projectSettings that we happen to be mutating, then we should also be mutating those keys in our plugins projectSettings?)

Or does it just really depend the specific usecase? The mental model I have in my head currently (which I want to verify) is that you would use buildSettings for keys which by default should be global for the build (but can still be mutated by specific sub projects) and projectSettings for keys which should be specified on a sbt project by project basis. There are also more nuanced questions arising from this, such as https://github.com/mdedetrich/sbt-apache-sonatype/pull/3/files#r1104113958

@eed3si9n
Copy link
Member

My comment regarding subproject > ThisBuild > Global (and thus prefer Global as default) applies mostly to plugins that have possibility to be used in a multi-project context, like sbt-assembly or sbt-native-packager.

For sbt-source-dist, I'd actually put everything in projectSettings and use the root subproject as the singleton holder.

lazy val root = (project in file("."))
  .enablePlugins(SourceDistPlugin)

should this mutation be done using projectSettings, buildSettings or globalSettings ... Or does it just really depend the specific usecase?

I think it depends.

  1. Prefer the weakest/widest scoping, and thus mirror the original.
  2. If your right-hand-side value requires baseDirectory etc, then you have to narrow to projectSettings.
  3. If the eventual consumer of the key only looks at Global-scoping like in the case of Global / concurrentRestrictions, you have no choice.

@mdedetrich
Copy link
Contributor

mdedetrich commented Feb 15, 2023

So I just had a discussion with @jrudolph and he clarified the confusion I had in my head specifically wrt to globalSettings which is that if we are dealing with keys that are created by plugin authors, there is a distinction that needs to be made between those keys which depend on other keys to set their default values and the keys which don't depend on any other keys to set their default values. The former should go either into buildSettings or projectSettings depending on various factors where as the latter should go into globalSettings.

Does it make sense to update the sbt website to make this more clear as there does seem to be various distinctions regarding best practice?

@eed3si9n
Copy link
Member

Does it make sense to update the sbt website to make this more clear as there does seem to be various distinctions regarding best practice?

Yea. If you have ideas on what you want to write, feel free to update https://github.com/sbt/website/blob/develop/src/reference/02-DetailTopics/05-Plugins-and-Best-Practices/03-Plugins-Best-Practices.md#scoping-advice.

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

No branches or pull requests

5 participants