Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/scalac_exec_group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# `scalac` exec group

Every rule that runs the Scalac compile action directly declares a Bazel
[`exec_group`][exec_groups] named `scalac`. Users can set execution properties
for the Scalac action independently of the target's other actions via
`exec_properties = {"scalac.<key>": "<value>"}` on the target.

Rules that declare the `scalac` exec group (target-level `exec_properties`
supported):

- `scala_library`
- `scala_macro_library`
- `scala_library_for_plugin_bootstrapping`
- `scala_binary`
- `scala_test`
- `scala_junit_test`
- `scala_repl` (only meaningful with `srcs`; most REPL targets have none)

`scala_doc` runs `scaladoc`, not `scalac`, and is not covered.

## Aspect-based rules

`scala_proto_library` and `scrooge_scala_library` run their Scalac compile
inside a Bazel aspect. Bazel does *not* propagate target-level
`exec_properties` to actions created by attached aspects, so setting
`exec_properties = {"scalac.<key>": ...}` on those targets does not work
(and fails at analysis time — the top-level rule intentionally does not
declare the exec group, to surface the limitation rather than silently
ignore the property). To configure the Scalac action for these rules, use
platform-level `exec_properties` on the [`platform`][platform_exec_properties]
that owns the aspect action.

## Example

Give the Scalac action of a specific `scala_library` more CPUs on a remote
executor:

```py
scala_library(
name = "big_lib",
srcs = glob(["*.scala"]),
exec_properties = {"scalac.cpu": "4"},
)
```

The `scalac.` prefix routes the property to the `scalac` exec group; only
the Scalac action sees it. Other actions produced by the same target (for
example, ijar) are unaffected.

`exec_properties` values are always strings per Bazel's schema — write
`"4"`, not `4`.

The specific key names after `scalac.` depend on the remote executor in use
(`cpu`, `container-image`, etc.) — consult your remote executor's docs.

[exec_groups]: https://bazel.build/extending/exec-groups
[platform_exec_properties]: https://bazel.build/reference/be/platforms-and-toolchains#platform.exec_properties
1 change: 1 addition & 0 deletions scala/private/rule_impls.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def compile_scala(
inputs = ins,
outputs = outs,
executable = scalac,
exec_group = "scalac",
mnemonic = "Scalac",
progress_message = "scala %s" % target_label,
execution_requirements = {"supports-workers": "1"},
Expand Down
1 change: 1 addition & 0 deletions scala/private/rules/scala_binary.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def make_scala_binary(*extras):
"//scala:toolchain_type",
"@bazel_tools//tools/jdk:toolchain_type",
],
exec_groups = {"scalac": exec_group()},
cfg = scala_version_transition,
provides = [JavaInfo],
implementation = _scala_binary_impl,
Expand Down
1 change: 1 addition & 0 deletions scala/private/rules/scala_junit_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def make_scala_junit_test(*extras):
"//testing/toolchain:testing_toolchain_type",
"@bazel_tools//tools/jdk:toolchain_type",
],
exec_groups = {"scalac": exec_group()},
cfg = scala_version_transition,
provides = [JavaInfo],
implementation = _scala_junit_test_impl,
Expand Down
3 changes: 3 additions & 0 deletions scala/private/rules/scala_library.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def make_scala_library(*extras):
"//scala:toolchain_type",
"@bazel_tools//tools/jdk:toolchain_type",
],
exec_groups = {"scalac": exec_group()},
cfg = scala_version_transition,
provides = [JavaInfo],
implementation = _scala_library_impl,
Expand Down Expand Up @@ -215,6 +216,7 @@ def make_scala_library_for_plugin_bootstrapping(*extras):
"//scala:toolchain_type",
"@bazel_tools//tools/jdk:toolchain_type",
],
exec_groups = {"scalac": exec_group()},
cfg = scala_version_transition,
provides = [JavaInfo],
implementation = _scala_library_for_plugin_bootstrapping_impl,
Expand Down Expand Up @@ -291,6 +293,7 @@ def make_scala_macro_library(*extras):
"//scala:toolchain_type",
"@bazel_tools//tools/jdk:toolchain_type",
],
exec_groups = {"scalac": exec_group()},
cfg = scala_version_transition,
provides = [JavaInfo],
implementation = _scala_macro_library_impl,
Expand Down
1 change: 1 addition & 0 deletions scala/private/rules/scala_repl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def make_scala_repl(*extras):
"//scala:toolchain_type",
"@bazel_tools//tools/jdk:toolchain_type",
],
exec_groups = {"scalac": exec_group()},
cfg = scala_version_transition,
provides = [JavaInfo],
implementation = _scala_repl_impl,
Expand Down
1 change: 1 addition & 0 deletions scala/private/rules/scala_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def make_scala_test(*extras):
"//testing/toolchain:testing_toolchain_type",
"@bazel_tools//tools/jdk:toolchain_type",
],
exec_groups = {"scalac": exec_group()},
cfg = scala_version_transition,
provides = [JavaInfo],
implementation = _scala_test_impl,
Expand Down
4 changes: 4 additions & 0 deletions scala_proto/private/scala_proto_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ def make_scala_proto_aspect(*extras):
extras_phases(extras),
*[extra["attrs"] for extra in extras if "attrs" in extra]
),
# Required so `compile_scala` can create the Scalac action with
# `exec_group = "scalac"`. Not user-configurable: Bazel does not
# propagate target-level exec_properties to aspect actions.
exec_groups = {"scalac": exec_group()},
toolchains = [
"//scala:toolchain_type",
"//scala_proto:toolchain_type",
Expand Down
71 changes: 71 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ load(
"scala_doc",
"scala_junit_test",
"scala_library",
"scala_library_for_plugin_bootstrapping",
"scala_library_suite",
"scala_macro_library",
"scala_repl",
Expand Down Expand Up @@ -753,6 +754,76 @@ scala_library(
deps = [":InlinableExported"],
)

# Regression targets for the "scalac" exec_group. Each sets a scalac.*
# exec_property, which Bazel validates against the rule's exec_groups at
# analysis time. Removing the exec_group from a rule fails these targets.
# See docs/scalac_exec_group.md.
#
# The `manual` tag on the *_test targets keeps `bazel test //test/...`
# from executing them — they exist to be analyzed, not run.
#
# test/shell/test_scalac_exec_group.sh additionally verifies via `bazel
# aquery` that the property actually reaches the Scalac action, which
# catches removal of `exec_group = "scalac"` from ctx.actions.run in
# compile_scala.

_SCALAC_EXEC_GROUP_PROPS = {"scalac.cpu": "1"}

scala_library(
name = "ScalacExecGroupLib",
srcs = ["Runtime.scala"],
exec_properties = _SCALAC_EXEC_GROUP_PROPS,
)

scala_macro_library(
name = "ScalacExecGroupMacro",
srcs = ["MacroTest.scala"],
exec_properties = _SCALAC_EXEC_GROUP_PROPS,
)

scala_library_for_plugin_bootstrapping(
name = "ScalacExecGroupLibForPluginBootstrapping",
srcs = ["Runtime.scala"],
exec_properties = _SCALAC_EXEC_GROUP_PROPS,
)

scala_binary(
name = "ScalacExecGroupBinary",
srcs = ["ScalaBinary.scala"],
exec_properties = _SCALAC_EXEC_GROUP_PROPS,
main_class = "scalarules.test.ScalaBinary",
deps = [
":HelloLib",
":MacroTest",
],
)

scala_test(
name = "ScalacExecGroupTest",
size = "small",
srcs = ["HelloLibTest.scala"],
exec_properties = _SCALAC_EXEC_GROUP_PROPS,
tags = ["manual"],
deps = [":HelloLib"],
)

scala_junit_test(
name = "ScalacExecGroupJunitTest",
size = "small",
srcs = ["src/main/scala/scalarules/test/junit/JunitNoTestEnvironmentTest.scala"],
exec_properties = _SCALAC_EXEC_GROUP_PROPS,
suffixes = ["Test"],
tags = ["manual"],
)

# scala_repl only invokes Scalac when srcs are present, so give this target
# a source to exercise the compile path.
scala_repl(
name = "ScalacExecGroupRepl",
srcs = ["A.scala"],
exec_properties = _SCALAC_EXEC_GROUP_PROPS,
)

TEST_ENV = {
"LOCATION": "West of House",
"DEP_PATH": "$(rootpath :HelloLib)",
Expand Down
56 changes: 56 additions & 0 deletions test/shell/test_scalac_exec_group.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash

# shellcheck source=./test_runner.sh

dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
. "${dir}"/test_runner.sh
. "${dir}"/test_helper.sh
runner=$(get_test_runner "${1:-local}")

# The regression targets in //test:ScalacExecGroup* set
# `exec_properties = {"scalac.cpu": "1"}`, which requires the rule to declare
# the "scalac" exec_group. Analysis alone catches removal of that rule-level
# declaration. This test additionally verifies via `bazel aquery` that the
# property actually reaches the Scalac action's ExecutionInfo, which catches
# removal of `exec_group = "scalac"` from `ctx.actions.run` in compile_scala
# (a change that would silently reroute the property to the default group).

SCALAC_EXEC_GROUP_TARGETS=(
//test:ScalacExecGroupLib
//test:ScalacExecGroupMacro
//test:ScalacExecGroupLibForPluginBootstrapping
//test:ScalacExecGroupBinary
//test:ScalacExecGroupTest
//test:ScalacExecGroupJunitTest
//test:ScalacExecGroupRepl
)

test_scalac_action_receives_exec_property() {
local target output
for target in "${SCALAC_EXEC_GROUP_TARGETS[@]}"; do
output="$(bazel aquery "mnemonic(\"Scalac\", ${target})" 2>&1)"
assert_matches 'ExecutionInfo:.*cpu: 1' "$output" \
"Scalac action on ${target} did not receive scalac.cpu=1 via exec_properties"
done
}

# scala_proto_library and scrooge_scala_library run Scalac via an aspect,
# and Bazel doesn't propagate target-level exec_properties to aspect actions.
# The top-level rules intentionally omit the scalac exec_group so users get
# an analysis error instead of a silently-dropped config — verify that.
ASPECT_RULE_TARGETS=(
//test_expect_failure/scalac_exec_group_on_aspect_rule:scala_proto_library_with_scalac_exec_property
//test_expect_failure/scalac_exec_group_on_aspect_rule:scrooge_scala_library_with_scalac_exec_property
)

test_scalac_exec_property_fails_on_aspect_rules() {
local target
for target in "${ASPECT_RULE_TARGETS[@]}"; do
action_should_fail_with_message \
'Tried to set properties for non-existent exec groups: scalac' \
build "${target}"
done
}

$runner test_scalac_action_receives_exec_property
$runner test_scalac_exec_property_fails_on_aspect_rules
21 changes: 21 additions & 0 deletions test_expect_failure/scalac_exec_group_on_aspect_rule/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load("//scala_proto:scala_proto.bzl", "scala_proto_library")
load("//twitter_scrooge:twitter_scrooge.bzl", "scrooge_scala_library")

# scala_proto_library and scrooge_scala_library run Scalac inside an aspect.
# Bazel doesn't propagate target-level exec_properties to aspect actions, so
# these rules intentionally do NOT declare the "scalac" exec_group — a user
# setting scalac.* here would silently drop the value. The lack of the
# exec_group turns that silent drop into an analysis-time error.
# See docs/scalac_exec_group.md and test/shell/test_scalac_exec_group.sh.

scala_proto_library(
name = "scala_proto_library_with_scalac_exec_property",
exec_properties = {"scalac.cpu": "1"},
deps = ["//test/proto3:generated-proto-lib"],
)

scrooge_scala_library(
name = "scrooge_scala_library_with_scalac_exec_property",
exec_properties = {"scalac.cpu": "1"},
deps = ["//test/src/main/scala/scalarules/test/twitter_scrooge/thrift"],
)
1 change: 1 addition & 0 deletions test_rules_scala.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ $runner bazel build //test_statsfile:SimpleNoStatsFile_statsfile --extra_toolcha
. "${test_dir}"/test_twitter_scrooge.sh
. "${test_dir}"/test_inherited_environment.sh
. "${test_dir}"/test_persistent_worker.sh
. "${test_dir}"/test_scalac_exec_group.sh
. "${test_dir}"/test_semanticdb.sh
. "${test_dir}"/test_scaladoc.sh
. "${test_dir}"/test_invalid_scalacopts.sh
Expand Down
4 changes: 4 additions & 0 deletions twitter_scrooge/twitter_scrooge.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ scrooge_scala_aspect = aspect(
),
},
),
# Required so `compile_scala` can create the Scalac action with
# `exec_group = "scalac"`. Not user-configurable: Bazel does not
# propagate target-level exec_properties to aspect actions.
exec_groups = {"scalac": exec_group()},
provides = [ScroogeAspectInfo],
required_aspect_providers = common_aspect_providers,
toolchains = common_toolchains,
Expand Down