Skip to content

Commit

Permalink
Toolchainize all testing toolchains
Browse files Browse the repository at this point in the history
Moves the Scalatest, JUnit, and Specs2 toolchains into
`@io_bazel_rules_scala_toolchains//testing`. Part of bazelbuild#1482.

Updates all `WORKSPACE` files to set the appropriate `scala_toolchains`
parameters and to remove the unnecessary repository import and toolchain
registration macros.

Adds a `fetch_sources_by_id` parameter to `repositories` from
`third_party/repositories/repositories.bzl`. This enables
`scala_toolchains` to build the `artifact_ids_to_fetch_sources` mapping
from artifact ID lists returned by new macros extracted from `WORKSPACE`
macros. The values assigned to each id match the original
`fetch_sources` settings in the corresponding original `WORKSPACE`
macros.

Updates `scala/scala_maven_import_external.bzl` to generate a `load`
line for `//scala:scala_import.bzl` based on the repo's canonical name,
not `@io_bazel_rules_scala`.

As usual, includes several other opportunistic removals of the
`@io_bazel_rules_scala` repo name prefix to avoid an internal dependency
on that name. This means Bzlmod users won't necessarily have to set the
`repo_name` parameter of `bazel_dep` when using `rules_scala`.

---

Introduces more macros to return a framework's Maven artifact
dependencies, rather than inlining them in a `repositories` call. These
inlined lists are replaced by macro invocations, and now the
`scala_toolchains` macro can invoke these macros to collect artifact IDs
to pass to `repositories`. This also allows for future changes to
introduce a `scala_version` parameter if necessary, similar to how
`scalafmt_artifact_ids` already works.

This is important to avoid collisions when creating repositories for
artifacts upon which more than one framework depends under Bzlmod.
`WORKSPACE` doesn't seem affected by these collisions, but Bzlmod will
produce errors like the following, where both `scala_proto` and
`twitter_scrooge` depend upon `io_bazel_rules_scala_guava`:

```txt
$ bazel build //src/...

ERROR: .../scala/scala_maven_import_external.bzl:299:24:
  Traceback (most recent call last):
    File ".../scala/extensions/deps.bzl",
      line 140, column 21, in _scala_deps_impl
        scala_toolchains(
    File ".../scala/private/macros/toolchains.bzl",
      line 140, column 17, in scala_toolchains
        _scrooge(
    File ".../twitter_scrooge/twitter_scrooge.bzl",
      line 96, column 17, in twitter_scrooge
        repositories(
    File ".../third_party/repositories/repositories.bzl",
      line 113, column 37, in repositories
        _scala_maven_import_external(
    File ".../scala/scala_maven_import_external.bzl",
      line 263, column 30, in scala_maven_import_external
        jvm_maven_import_external(
    File ".../scala/scala_maven_import_external.bzl",
      line 299, column 24, in jvm_maven_import_external
        jvm_import_external(jar_urls = jar_urls, srcjar_urls = srcjar_urls, coordinates = artifact, **kwargs)

Error in repository_rule: A repo named
  io_bazel_rules_scala_guava_2_13_15
  is already generated by this module extension at
  .../scala/scala_maven_import_external.bzl:299:24

ERROR: Analysis of target
  '//src/java/io/bazel/rulesscala/worker:worker_test'
  failed; build aborted:
  error evaluating module extension scala_deps in
  //scala/extensions:deps.bzl
```

Recent updates to `scripts/create_repository.py` (bazelbuild#1639, bazelbuild#1642) make it
easy to emit full direct dependency lists for artifacts included in
`third_party/repositories/scala_*.bzl`. This increases the likelihood of
collisions, since this expanded metadata forces the macros that
instantiate artifact repos to instantiate even more repos.

By fetching list of artifact IDs from these macros, `scala_toolchains`
can now consolidate them into dictionary keys. Then it passes these
unique keys to `repositories` directly, avoiding the problem of
instantiating the same repo multiple times in the same module extension.

This, in turn, also avoids the need to add parameters to the original
`WORKSPACE` macros that instantiate dependencies to avoid collisions
under Bzlmod. The `scala_toolchains` macro never needs to call these
original macros, under either `WORKSPACE` or Bzlmod.

Finally, it also reduces duplication between these artifact ID lists and
the `_*_DEPS` symbols originally from `testing/BUILD` (and now in
`testing/deps.bzl`). The dependency labels are now generated
programatically.

(Aside: As I mentioned, we may eventually need to pass a Scala version
argument to these macros. It will be possible to cross that bridge
without too much trouble if and when that day comes. Or I can try to
future proof it in a follow up pull request.)
  • Loading branch information
mbland committed Dec 8, 2024
1 parent 8d9e1f8 commit ca44dc6
Show file tree
Hide file tree
Showing 26 changed files with 293 additions and 241 deletions.
14 changes: 4 additions & 10 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ scala_config(enable_compiler_dependency_tracking = True)

load("//scala:scala.bzl", "scala_toolchains")

scala_toolchains(fetch_sources = True)
scala_toolchains(
fetch_sources = True,
testing = True,
)

register_toolchains(
"//testing:testing_toolchain",
"//scala:unused_dependency_checker_error_toolchain",
"//test/proto:scalapb_toolchain",
"@io_bazel_rules_scala_toolchains//...:all",
Expand Down Expand Up @@ -69,14 +71,6 @@ load("//scala_proto:scala_proto.bzl", "scala_proto_repositories")

scala_proto_repositories()

load("//scalatest:scalatest.bzl", "scalatest_repositories")

scalatest_repositories()

load("//specs2:specs2_junit.bzl", "specs2_junit_repositories")

specs2_junit_repositories()

load("//scala/scalafmt:scalafmt_repositories.bzl", "scalafmt_default_config", "scalafmt_repositories")

scalafmt_default_config()
Expand Down
8 changes: 1 addition & 7 deletions examples/crossbuild/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ scala_config(

load("@io_bazel_rules_scala//scala:scala.bzl", "scala_toolchains")

scala_toolchains()
scala_toolchains(scalatest = True)

register_toolchains("@io_bazel_rules_scala_toolchains//...:all")

Expand All @@ -59,9 +59,3 @@ rules_proto_toolchains()
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")

protobuf_deps()

load("@io_bazel_rules_scala//testing:scalatest.bzl", "scalatest_repositories", "scalatest_toolchain")

scalatest_repositories()

scalatest_toolchain()
6 changes: 2 additions & 4 deletions examples/testing/multi_frameworks_toolchain/BUILD
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
load("@io_bazel_rules_scala//scala:scala.bzl", "setup_scala_testing_toolchain")
load("@io_bazel_rules_scala//scala:scala_cross_version.bzl", "version_suffix")
load("@io_bazel_rules_scala_config//:config.bzl", "SCALA_VERSION")

setup_scala_testing_toolchain(
name = "testing_toolchain",
junit_classpath = [
"@io_bazel_rules_scala_junit_junit",
"@io_bazel_rules_scala_org_hamcrest_hamcrest_core",
],
scalatest_classpath = [dep + version_suffix(SCALA_VERSION) for dep in [
scalatest_classpath = [
"@io_bazel_rules_scala_scalactic",
"@io_bazel_rules_scala_scalatest",
"@io_bazel_rules_scala_scalatest_compatible",
Expand All @@ -21,7 +19,7 @@ setup_scala_testing_toolchain(
"@io_bazel_rules_scala_scalatest_matchers_core",
"@io_bazel_rules_scala_scalatest_mustmatchers",
"@io_bazel_rules_scala_scalatest_shouldmatchers",
]],
],
specs2_classpath = [
"@io_bazel_rules_scala_org_specs2_specs2_common",
"@io_bazel_rules_scala_org_specs2_specs2_core",
Expand Down
15 changes: 4 additions & 11 deletions examples/testing/multi_frameworks_toolchain/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ scala_config()

load("@io_bazel_rules_scala//scala:scala.bzl", "scala_toolchains")

scala_toolchains(fetch_sources = True)
scala_toolchains(
fetch_sources = True,
testing = True,
)

register_toolchains(
":testing_toolchain",
Expand All @@ -55,13 +58,3 @@ rules_proto_toolchains()
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")

protobuf_deps()

load("@io_bazel_rules_scala//testing:scalatest.bzl", "scalatest_repositories")
load("@io_bazel_rules_scala//testing:junit.bzl", "junit_repositories")
load("@io_bazel_rules_scala//testing:specs2_junit.bzl", "specs2_junit_repositories")

scalatest_repositories()

junit_repositories()

specs2_junit_repositories()
11 changes: 4 additions & 7 deletions examples/testing/scalatest_repositories/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ scala_config()

load("@io_bazel_rules_scala//scala:scala.bzl", "scala_toolchains")

scala_toolchains(fetch_sources = True)
scala_toolchains(
fetch_sources = True,
scalatest = True,
)

register_toolchains("@io_bazel_rules_scala_toolchains//...:all")

Expand All @@ -52,9 +55,3 @@ rules_proto_toolchains()
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")

protobuf_deps()

load("@io_bazel_rules_scala//testing:scalatest.bzl", "scalatest_repositories", "scalatest_toolchain")

scalatest_repositories()

scalatest_toolchain()
11 changes: 4 additions & 7 deletions examples/testing/specs2_junit_repositories/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ scala_config()

load("@io_bazel_rules_scala//scala:scala.bzl", "scala_toolchains")

scala_toolchains(fetch_sources = True)
scala_toolchains(
fetch_sources = True,
specs2 = True,
)

register_toolchains("@io_bazel_rules_scala_toolchains//...:all")

Expand All @@ -52,9 +55,3 @@ rules_proto_toolchains()
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")

protobuf_deps()

load("@io_bazel_rules_scala//testing:specs2_junit.bzl", "specs2_junit_repositories", "specs2_junit_toolchain")

specs2_junit_repositories()

specs2_junit_toolchain()
11 changes: 7 additions & 4 deletions junit/junit.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ load(
)
load("//third_party/repositories:repositories.bzl", "repositories")

def junit_artifact_ids():
return [
"io_bazel_rules_scala_junit_junit",
"io_bazel_rules_scala_org_hamcrest_hamcrest_core",
]

def junit_repositories(
maven_servers = _default_maven_server_urls(),
fetch_sources = True):
repositories(
for_artifact_ids = [
"io_bazel_rules_scala_junit_junit",
"io_bazel_rules_scala_org_hamcrest_hamcrest_core",
],
for_artifact_ids = junit_artifact_ids(),
fetch_sources = fetch_sources,
maven_servers = maven_servers,
)
66 changes: 64 additions & 2 deletions scala/private/macros/toolchains.bzl
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
"""Macro to instantiate @io_bazel_rules_scala_toolchains"""

load(":macros/toolchains_repo.bzl", "scala_toolchains_repo")
load("//junit:junit.bzl", "junit_artifact_ids")
load("//scala/private:macros/scala_repositories.bzl", "scala_repositories")
load("//scala:scala_cross_version.bzl", "default_maven_server_urls")
load("//scalatest:scalatest.bzl", "scalatest_artifact_ids")
load("//specs2:specs2.bzl", "specs2_artifact_ids")
load("//specs2:specs2_junit.bzl", "specs2_junit_artifact_ids")
load("//third_party/repositories:repositories.bzl", "repositories")
load("@io_bazel_rules_scala_config//:config.bzl", "SCALA_VERSIONS")

def scala_toolchains(
maven_servers = default_maven_server_urls(),
Expand All @@ -11,7 +17,11 @@ def scala_toolchains(
load_scala_toolchain_dependencies = True,
fetch_sources = False,
validate_scala_version = True,
scala_compiler_srcjars = {}):
scala_compiler_srcjars = {},
scalatest = False,
junit = False,
specs2 = False,
testing = False):
"""Instantiates @io_bazel_rules_scala_toolchains and all its dependencies.
Provides a unified interface to configuring rules_scala both directly in a
Expand Down Expand Up @@ -51,6 +61,11 @@ def scala_toolchains(
compiler srcjar metadata dictionaries containing:
- exactly one "label", "url", or "urls" key
- optional "integrity" or "sha256" keys
scalatest: whether to instantiate the Scalatest toolchain
junit: whether to instantiate the JUnit toolchain
specs2: whether to instantiate the Specs2 JUnit toolchain
testing: whether to instantiate the Scalatest, JUnit, and Specs2 JUnit
toolchains combined
"""
scala_repositories(
maven_servers = maven_servers,
Expand All @@ -63,4 +78,51 @@ def scala_toolchains(
scala_compiler_srcjars = scala_compiler_srcjars,
)

scala_toolchains_repo()
if testing:
scalatest = True
junit = True
specs2 = True
if specs2:
junit = True

artifact_ids_to_fetch_sources = {}

if scalatest:
artifact_ids_to_fetch_sources.update({
id: True
for id in scalatest_artifact_ids()
})
if junit:
artifact_ids_to_fetch_sources.update({
id: True
for id in junit_artifact_ids()
})
if specs2:
artifact_ids_to_fetch_sources.update({
id: True
for id in specs2_artifact_ids() + specs2_junit_artifact_ids()
})

for scala_version in SCALA_VERSIONS:
version_specific_artifact_ids = {}

all_artifacts = (
artifact_ids_to_fetch_sources | version_specific_artifact_ids
)

repositories(
scala_version = scala_version,
for_artifact_ids = all_artifacts.keys(),
maven_servers = maven_servers,
fetch_sources = fetch_sources,
fetch_sources_by_id = all_artifacts,
overriden_artifacts = overridden_artifacts,
validate_scala_version = validate_scala_version,
)

scala_toolchains_repo(
scalatest = scalatest,
junit = junit,
specs2 = specs2,
testing = testing,
)
67 changes: 67 additions & 0 deletions scala/private/macros/toolchains_repo.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
"""Repository rule to instantiate @io_bazel_rules_scala_toolchains"""

def _generate_testing_toolchain_build_file_args(repo_attr):
framework_deps = {}

if repo_attr.testing:
framework_deps = {
"scalatest": "SCALATEST_DEPS",
"junit": "JUNIT_DEPS",
"specs2": "SPECS2_DEPS",
"specs2_junit": "SPECS2_JUNIT_DEPS",
}
if repo_attr.scalatest:
framework_deps["scalatest"] = "SCALATEST_DEPS"
if repo_attr.specs2:
framework_deps["specs2"] = "SPECS2_DEPS"
framework_deps["specs2_junit"] = "SPECS2_JUNIT_DEPS"
framework_deps["junit"] = "JUNIT_DEPS"
if repo_attr.junit:
framework_deps["junit"] = "JUNIT_DEPS"

if len(framework_deps) == 0:
return None

# The _TESTING_TOOLCHAIN_BUILD template expects that all framework keys are
# present in the dictionary, so it can set unset framework classpath
# parameters to `None`.
return {
"deps_symbols": "\",\n \"".join(
[s for s in framework_deps.values()],
),
"scalatest": framework_deps.get("scalatest"),
"junit": framework_deps.get("junit"),
"specs2": framework_deps.get("specs2"),
"specs2_junit": framework_deps.get("specs2_junit"),
}

def _scala_toolchains_repo_impl(repository_ctx):
repo_attr = repository_ctx.attr
format_args = {
Expand All @@ -10,6 +45,11 @@ def _scala_toolchains_repo_impl(repository_ctx):
if repo_attr.scala:
toolchains["scala"] = _SCALA_TOOLCHAIN_BUILD

testing_build_args = _generate_testing_toolchain_build_file_args(repo_attr)
if testing_build_args != None:
format_args.update(testing_build_args)
toolchains["testing"] = _TESTING_TOOLCHAIN_BUILD

if len(toolchains) == 0:
fail("no toolchains specified")

Expand All @@ -22,8 +62,13 @@ def _scala_toolchains_repo_impl(repository_ctx):

_scala_toolchains_repo = repository_rule(
implementation = _scala_toolchains_repo_impl,
doc = "Creates a repo containing Scala toolchain packages",
attrs = {
"scala": attr.bool(default = True),
"scalatest": attr.bool(),
"junit": attr.bool(),
"specs2": attr.bool(),
"testing": attr.bool(),
},
)

Expand Down Expand Up @@ -73,3 +118,25 @@ load(
]
]
"""

_TESTING_TOOLCHAIN_BUILD = """
load("@@{rules_scala_repo}//scala:scala.bzl", "setup_scala_testing_toolchain")
load("@@{rules_scala_repo}//scala:scala_cross_version.bzl", "version_suffix")
load(
"@@{rules_scala_repo}//testing:deps.bzl",
"{deps_symbols}",
)
load("@io_bazel_rules_scala_config//:config.bzl", "SCALA_VERSIONS")
[
setup_scala_testing_toolchain(
name = "testing_toolchain" + version_suffix(scala_version),
scala_version = scala_version,
scalatest_classpath = {scalatest},
junit_classpath = {junit},
specs2_classpath = {specs2},
specs2_junit_classpath = {specs2_junit},
)
for scala_version in SCALA_VERSIONS
]
"""
Loading

0 comments on commit ca44dc6

Please sign in to comment.