Skip to content

Commit fae42a4

Browse files
Jamie5Andre Rocha
authored and
Andre Rocha
committed
Update dependency configuration options (bazel-contrib#995)
* Update dependency configuration * lint * comments * comment * comments
1 parent 983392b commit fae42a4

File tree

12 files changed

+334
-68
lines changed

12 files changed

+334
-68
lines changed

README.md

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -158,31 +158,75 @@ replacements:
158158
"@io_bazel_rules_scala_scala_xml//:io_bazel_rules_scala_scala_xml"
159159
```
160160
161-
## [Experimental] Using strict-deps
162-
Bazel pushes towards explicit and minimal dependencies to keep BUILD file hygiene and allow for targets to refactor their dependencies without fear of downstream breaking.
163-
Currently rules_scala does this at the cost of having cryptic `scalac` errors when one mistakenly depends on a transitive dependency or, as more often the case for some, a transitive dependency is needed to [please scalac](https://github.com/scalacenter/advisoryboard/blob/master/proposals/009-improve-direct-dependency-experience.md) itself.
164-
To learn more about the motivation of strict-deps itself you can visit this Bazel blog [post](https://blog.bazel.build/2017/06/28/sjd-unused_deps.html) on the subject.
161+
## [Experimental] Dependency options
165162
166-
To use it just add `--strict_java_deps=WARN|ERROR` to your `bazel` invocation.
167-
In both cases of `WARN` or `ERROR` you will get the following text in the event of a violation:
163+
There are a number of dependency options which can be set in the scala toolchain. These include `dependency_mode`, `strict_deps_mode`, `unused_dependency_checker_mode`, and `dependency_tracking_method`.
164+
165+
### [Experimental] Recommended options
166+
167+
We recommend one of the following sets of options
168+
169+
**Option A**
170+
Accept the defaults, which might work well enough for you. The defaults are
168171
```
169-
...
170-
Target '//some_package:transitive_dependency' is used but isn't explicitly declared, please add it to the deps.
171-
You can use the following buildozer command:
172-
buildozer 'add deps //some_package:transitive_dependency' //some_other_package:transitive_dependency_user
172+
dependency_mode = "direct",
173+
strict_deps_mode = "off",
174+
unused_dependency_checker_mode = "off",
175+
dependency_tracking_method = "high-level",
176+
```
177+
but you do not need to include this in the toolchain as they are the defaults.
178+
179+
**Option B**
180+
```
181+
dependency_mode = "plus-one",
182+
strict_deps_mode = "error",
183+
unused_dependency_checker_mode = "error",
184+
dependency_tracking_method = "ast",
173185
```
174-
Note that if you have `buildozer` installed you can just run the last line and have it automatically apply the fix for you.
175186
176-
**Caveats:**
187+
Should the first option result in too much effort in handling build files and the like due to confusing dependencies and you becoming confused as to why some specific dependency is needed when the code being compiled never references it, consider this set of options. It will include both dependencies and dependencies of dependencies, which in practice is enough to stop almost all strange missing dependency errors at the cost of somewhat more incremental compile cost in certain cases.
188+
189+
With these settings, we also will error on dependencies which are unneeded, and dependencies which should be included in `deps` due to be directly referenced in the code, but are not.
190+
191+
The dependency tracking method `ast` is experimental but so far proves to be better than the default for computing the direct dependencies for `plus-one` mode code. In the future we hope to make this the default for `plus-one` mode and remove the option altogether.
192+
193+
### [Experimental] Dependency mode
194+
195+
There are three dependency modes. The reason for the multiple modes is that often `scalac` depends on jars which seem unnecessary at first glance. Hence, in order to reduce the need to please `scalac`, we provide the following options.
196+
- `dependency_mode = "direct"` - only include direct dependencies during compiliation; that is, those in the `deps` attribute
197+
- `dependency_mode = "plus-one"` - only include `deps` and `deps` of `deps` during compiliation.
198+
- `dependency_mode = "transitive"` - all transitive dependencies are included during compiliation. That is, `deps`, `deps` of `deps`, `deps` of `deps` of `deps`, and so on.
199+
200+
Note when a dependency is included, that means its jars are included on the classpath, along with the jars of any targets that it exports.
201+
202+
When using `direct` mode, there can be cryptic `scalac` errors when one mistakenly depends on a transitive dependency or, as more often the case for some, a transitive dependency is needed to [please scalac](https://github.com/scalacenter/advisoryboard/blob/master/proposals/009-improve-direct-dependency-experience.md) itself.
203+
204+
As one goes down the list, more dependencies are included which helps reduce confusing requirements to add `deps`, at the cost of increased incremental builds due to a greater number of dependencies. In practice, using `plus-one` deps results in almost no confusing `deps` entries required while still being relatively small in terms of the number of total dependencies included.
205+
206+
**Caveats for `plus_one` and `transitive`:**
177207
<ul>
178-
<li>Extra builds- when strict-deps is on the transitive dependencies are inputs to the compilation action which means you can potentially have more build triggers for changes the cross the ijar boundary </li>
208+
<li>Extra builds- Extra dependencies are inputs to the compilation action which means you can potentially have more build triggers for changes the cross the ijar boundary </li>
179209
<li>Label propagation- since label of targets are needed for the clear message and since it's not currently supported by JavaInfo from bazel we manually propagate it. This means that the error messages have a significantly lower grade if you don't use one of the scala rules or scala_import (since they don't propagate these labels)</li>
180210
<li>javac outputs incorrect targets due to a problem we're tracing down. Practically we've noticed it's pretty trivial to understand the correct target (i.e. it's almost a formatting problem) </li>
181211
</ul>
182212
183-
Note: Currently strict-deps is protected by a feature toggle but we're strongly considering making it the default behavior as `java_*` rules do.
213+
Note: the last two issues are bugs which will be addressed by [https://github.com/bazelbuild/rules_scala/issues/839].
214+
215+
### [Experimental] Strict deps mode
216+
We have a strict dependency checker which requires that any type referenced in the sources of a scala target should be included in that rule's deps. To learn about the motivation for this you can visit this Bazel blog [post](https://blog.bazel.build/2017/06/28/sjd-unused_deps.html) on the subject.
184217
185-
## [Experimental] Unused dependency checking
218+
The option `strict_deps_mode` can be set to `off`, `warn`, or `error`. We highly recommend setting it to `error`.
219+
220+
In both cases of `warn` or `error` you will get the following text in the event of a violation:
221+
```
222+
...
223+
Target '//some_package:transitive_dependency' is used but isn't explicitly declared, please add it to the deps.
224+
You can use the following buildozer command:
225+
buildozer 'add deps //some_package:transitive_dependency' //some_other_package:transitive_dependency_user
226+
```
227+
Note that if you have `buildozer` installed you can just run the last line and have it automatically apply the fix for you.
228+
229+
### [Experimental] Unused dependency checking
186230
To allow for better caching and faster builds we want to minimize the direct dependencies of our targets. Unused dependency checking
187231
makes sure that all targets specified as direct dependencies are actually used. If `unused_dependency_checker_mode` is set to either
188232
`error` or `warn` you will get the following message for any dependencies that are not used:
@@ -192,12 +236,38 @@ You can use the following buildozer command:
192236
buildozer 'remove deps //some_package:unused_dep' //target:target
193237
```
194238
195-
Currently unused dependency checking and strict-deps can't be used simultaneously, if both are set only strict-deps will run.
196-
197239
Unused dependency checking can either be enabled globally for all targets using a scala toolchain or for individual targets using the
198-
`unused_dependency_checker_mode` attribute. The feature is still experimental and there can thus be cases where it works incorrectly,
199-
in these cases you can enable unused dependency checking globally through a toolchain and override individual misbehaving targets
200-
using the attribute.
240+
`unused_dependency_checker_mode` attribute.
241+
242+
The feature is still experimental and there can thus be cases where it works incorrectly, in these cases you can enable unused dependency checking globally through a toolchain and disable reports of individual misbehaving targets with `unused_dependency_checker_ignored_targets` which is a list of labels.
243+
244+
### [Experimental] Dependency tracking method
245+
246+
The strict dependency tracker and unused dependency tracker need to track the used dependencies of a scala compilation unit. This toggle allows one to pick which method of tracking to use.
247+
248+
- `dependency_tracking_method = "high-level"` - This is the existing tracking method which has false positives and negatives but generally works reasonably well for `direct` dependency mode.
249+
- `dependency_tracking_method = "ast"` - This is a new tracking method which is being developed for `plus-one` and `transitive` dependency modes. It is still being developed and may have issues which need fixing. If you discover an issue, please submit a small repro of the problem.
250+
251+
Note we intend to eventually remove this flag and use `high-level` as the method for `direct` dependency mode, and `ast` as the method for `plus-one` and `transitive` dependency modes.
252+
253+
In the meantime, if you are using `plus-one` or `transitive` dependency modes, you can use `ast` dependency tracking mode and see how well it works for you.
254+
255+
### [Experimental] Turning on strict_deps_mode/unused_dependency_checker_mode
256+
257+
It can be daunting to turn on strict deps checking or unused dependency mode checking on a large codebase. However, it need not be so bad if this is done in phases
258+
259+
1. Have a default scala toolchain `A` with the option of interest set to `off` (the starting state)
260+
2. Create a second scala toolchain `B` with the option of interest set to `warn` or `error`. Those who are working on enabling the flag can run with this toolchain as a command line argument to help identify issues and fix them.
261+
3. Once all issues are fixed, change `A` to have the option of interest set to `error` and delete `B`.
262+
263+
We recommend turning on strict_deps_mode first, as rule `A` might have an entry `B` in its `deps`, and `B` in turn depends on `C`. Meanwhile, the code of `A` only uses `C` but not `B`. Hence, the unused dependency checker, if on, will request that `B` be removed from `A`'s deps. But this will lead to a compile error as `A` can no longer depend on `C`. However, if strict dependency checking was on, then `A`'s deps is guaranteed to have `C` in it.
264+
265+
### [Experimental] Migrating from deprecated configurations
266+
267+
There are a few deprecated configuration methods which we will be removing in the near future.
268+
269+
- `plus_one_deps_mode = "on"` on the scala toolchain. Instead, set `dependency_mode = "plus-one"` on the scala toolchain. `plus_one_deps_mode` will be removed in the future.
270+
- The command line argument `--strict_java_deps=WARN/ERROR`. Instead, set `dependency_mode = "transitive"` on the scala toolchain, and if only a warning is desired set `strict_deps_mode = "warn"` on the toolchain. In the future, `strict_java_deps` will no longer affect how scala files are compiled. Note that `strict_java_deps` will still control java compilation.
201271
202272
## Advanced configurable rules
203273
To make the ruleset more flexible and configurable, we introduce a phase architecture. By using a phase architecture, where rule implementations are defined as a list of phases that are executed sequentially, functionality can easily be added (or modified) by adding (or swapping) phases.

scala/plusone.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ PlusOneDeps = provider(
1010
)
1111

1212
def _collect_plus_one_deps_aspect_impl(target, ctx):
13-
if (ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].plus_one_deps_mode == "off"):
13+
if (ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].dependency_mode != "plus-one"):
1414
return []
1515
export_plus_one_deps = []
1616
for exported_dep in getattr(ctx.rule.attr, "exports", []):

scala/private/dependency.bzl

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ def new_dependency_info(
88
strict_deps_mode,
99
dependency_tracking_method):
1010
is_strict_deps_on = strict_deps_mode != "off"
11-
12-
# Currently we do not support running both strict and unused deps at the same time
13-
if is_strict_deps_on:
14-
unused_deps_mode = "off"
15-
1611
is_unused_deps_on = unused_deps_mode != "off"
1712

1813
need_direct_jars = is_strict_deps_on or is_unused_deps_on
@@ -24,7 +19,7 @@ def new_dependency_info(
2419
need_direct_jars = need_direct_jars,
2520
need_direct_targets = need_direct_targets,
2621
need_direct_info = need_direct_jars or need_direct_targets,
27-
dependency_tracking_method = "high-level",
22+
dependency_tracking_method = dependency_tracking_method,
2823
unused_deps_mode = unused_deps_mode,
2924
strict_deps_mode = strict_deps_mode,
3025
use_analyzer = is_strict_deps_on or is_unused_deps_on,
@@ -41,7 +36,7 @@ def legacy_unclear_dependency_info_for_protobuf_scrooge(ctx):
4136

4237
# TODO(https://github.com/bazelbuild/rules_scala/issues/987): Clariy the situation
4338
def _legacy_unclear_dependency_mode_for_protobuf_scrooge(ctx):
44-
if is_strict_deps_on(ctx):
39+
if _is_strict_deps_on(ctx):
4540
return "transitive"
4641
else:
4742
return "direct"
@@ -50,11 +45,7 @@ def get_strict_deps_mode(ctx):
5045
if not hasattr(ctx.attr, "_dependency_analyzer_plugin"):
5146
return "off"
5247

53-
# when the strict deps FT is removed the "default" check
54-
# will be removed since "default" will mean it's turned on
55-
if ctx.fragments.java.strict_java_deps == "default":
56-
return "off"
57-
return ctx.fragments.java.strict_java_deps
48+
return ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].strict_deps_mode
5849

59-
def is_strict_deps_on(ctx):
50+
def _is_strict_deps_on(ctx):
6051
return get_strict_deps_mode(ctx) != "off"

scala/private/phases/phase_dependency.bzl

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
load(
44
"@io_bazel_rules_scala//scala/private:dependency.bzl",
55
"get_strict_deps_mode",
6-
"is_strict_deps_on",
76
"new_dependency_info",
87
)
98
load(
@@ -35,6 +34,8 @@ def _phase_dependency(
3534
p,
3635
unused_deps_always_off,
3736
strict_deps_always_off):
37+
toolchain = ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"]
38+
3839
if strict_deps_always_off:
3940
strict_deps_mode = "off"
4041
else:
@@ -52,26 +53,12 @@ def _phase_dependency(
5253
unused_deps_mode = "off"
5354

5455
return new_dependency_info(
55-
_get_dependency_mode(ctx),
56+
toolchain.dependency_mode,
5657
unused_deps_mode,
5758
strict_deps_mode,
58-
"high-level",
59+
toolchain.dependency_tracking_method,
5960
)
6061

61-
def _is_plus_one_deps_on(ctx):
62-
return ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"].plus_one_deps_mode != "off"
63-
64-
def _get_dependency_mode(ctx):
65-
if is_strict_deps_on(ctx):
66-
# all transitive dependencies are included
67-
return "transitive"
68-
elif _is_plus_one_deps_on(ctx):
69-
# dependencies and dependencies of dependencies are included
70-
return "plus-one"
71-
else:
72-
# only explicitly-specified dependencies are included
73-
return "direct"
74-
7562
def _get_unused_deps_mode(ctx):
7663
if ctx.attr.unused_dependency_checker_mode:
7764
return ctx.attr.unused_dependency_checker_mode

scala/scala_toolchain.bzl

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,74 @@ load(
33
_ScalacProvider = "ScalacProvider",
44
)
55

6+
def _compute_dependency_mode(input_dependency_mode, input_plus_one_deps_mode):
7+
if input_plus_one_deps_mode == "on":
8+
return "plus-one"
9+
10+
if input_dependency_mode == "":
11+
return "direct"
12+
13+
return input_dependency_mode
14+
15+
def _compute_strict_deps_mode(input_strict_deps_mode, dependency_mode):
16+
if dependency_mode == "direct":
17+
return "off"
18+
if input_strict_deps_mode == "default":
19+
if dependency_mode == "transitive":
20+
return "error"
21+
else:
22+
return "off"
23+
return input_strict_deps_mode
24+
25+
def _compute_dependency_tracking_method(input_dependency_tracking_method):
26+
if input_dependency_tracking_method == "default":
27+
return "high-level"
28+
return input_dependency_tracking_method
29+
630
def _scala_toolchain_impl(ctx):
31+
if ctx.attr.plus_one_deps_mode != "":
32+
print(
33+
"Setting plus_one_deps_mode on toolchain is deprecated." +
34+
"Use 'dependency_mode = \"plus-one\"' instead",
35+
)
36+
if ctx.attr.dependency_mode != "" and ctx.attr.plus_one_deps_mode != "":
37+
fail("Cannot set both dependency_mode and plus_one_deps_mode on toolchain")
38+
39+
if ctx.fragments.java.strict_java_deps != "default" and ctx.fragments.java.strict_java_deps != "off":
40+
dependency_mode = "transitive"
41+
strict_deps_mode = ctx.fragments.java.strict_java_deps
42+
unused_dependency_checker_mode = "off"
43+
dependency_tracking_method = "high-level"
44+
else:
45+
dependency_mode = _compute_dependency_mode(
46+
ctx.attr.dependency_mode,
47+
ctx.attr.plus_one_deps_mode,
48+
)
49+
strict_deps_mode = _compute_strict_deps_mode(
50+
ctx.attr.strict_deps_mode,
51+
dependency_mode,
52+
)
53+
54+
unused_dependency_checker_mode = ctx.attr.unused_dependency_checker_mode
55+
dependency_tracking_method = _compute_dependency_tracking_method(ctx.attr.dependency_tracking_method)
56+
57+
# Final quality checks to possibly detect buggy code above
58+
if dependency_mode not in ("direct", "plus-one", "transitive"):
59+
fail("Internal error: invalid dependency_mode " + dependency_mode)
60+
61+
if strict_deps_mode not in ("off", "warn", "error"):
62+
fail("Internal error: invalid strict_deps_mode " + strict_deps_mode)
63+
64+
if dependency_tracking_method not in ("ast", "high-level"):
65+
fail("Internal error: invalid dependency_tracking_method " + dependency_tracking_method)
66+
767
toolchain = platform_common.ToolchainInfo(
868
scalacopts = ctx.attr.scalacopts,
969
scalac_provider_attr = ctx.attr.scalac_provider_attr,
10-
unused_dependency_checker_mode = ctx.attr.unused_dependency_checker_mode,
11-
plus_one_deps_mode = ctx.attr.plus_one_deps_mode,
70+
dependency_mode = dependency_mode,
71+
strict_deps_mode = strict_deps_mode,
72+
unused_dependency_checker_mode = unused_dependency_checker_mode,
73+
dependency_tracking_method = dependency_tracking_method,
1274
enable_code_coverage_aspect = ctx.attr.enable_code_coverage_aspect,
1375
scalac_jvm_flags = ctx.attr.scalac_jvm_flags,
1476
scala_test_jvm_flags = ctx.attr.scala_test_jvm_flags,
@@ -23,13 +85,23 @@ scala_toolchain = rule(
2385
default = "@io_bazel_rules_scala//scala:scalac_default",
2486
providers = [_ScalacProvider],
2587
),
88+
"dependency_mode": attr.string(
89+
values = ["direct", "plus-one", "transitive", ""],
90+
),
91+
"strict_deps_mode": attr.string(
92+
default = "default",
93+
values = ["off", "warn", "error", "default"],
94+
),
2695
"unused_dependency_checker_mode": attr.string(
2796
default = "off",
2897
values = ["off", "warn", "error"],
2998
),
99+
"dependency_tracking_method": attr.string(
100+
default = "default",
101+
values = ["ast", "high-level", "default"],
102+
),
30103
"plus_one_deps_mode": attr.string(
31-
default = "off",
32-
values = ["off", "on"],
104+
values = ["off", "on", ""],
33105
),
34106
"enable_code_coverage_aspect": attr.string(
35107
default = "off",
@@ -38,4 +110,5 @@ scala_toolchain = rule(
38110
"scalac_jvm_flags": attr.string_list(),
39111
"scala_test_jvm_flags": attr.string_list(),
40112
},
113+
fragments = ["java"],
41114
)

test/scala_test/A.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
import org.scalatest._
3+
4+
abstract class A extends FunSuite {
5+
val Number: Int
6+
7+
test("number is positive") {
8+
assert(Number > 0)
9+
}
10+
}

test/scala_test/B.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
class B extends A {
3+
override val Number: Int = 12
4+
}

test/scala_test/BUILD

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_test")
2+
3+
scala_test(
4+
name = "a",
5+
srcs = ["A.scala"],
6+
)
7+
8+
scala_test(
9+
name = "b",
10+
srcs = ["B.scala"],
11+
deps = [":a"],
12+
)

0 commit comments

Comments
 (0)