diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 49c937177..4eef9bc08 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -29,17 +29,14 @@ tasks: # Install xmllint - sudo apt update && sudo apt install --reinstall libxml2-utils -y - "./test_rules_scala.sh" - # Switch `last_rc` to `last_green` once Bzlmod lands. - # https://github.com/bazelbuild/rules_scala/issues/1482 - test_rules_scala_linux_last_rc: - name: "./test_rules_scala (last_rc Bazel)" + test_rules_scala_linux_last_green: + name: "./test_rules_scala (last_green Bazel)" platform: ubuntu2004 - bazel: last_rc + bazel: last_green shell_commands: # Install xmllint - sudo apt update && sudo apt install --reinstall libxml2-utils -y - - echo "build --enable_workspace" >> .bazelrc - - "./test_rules_scala.sh || buildkite-agent annotate --style 'warning' \"Optional build with last_rc Bazel version failed, [see here](${BUILDKITE_BUILD_URL}#${BUILDKITE_JOB_ID}) (It is not mandatory but worth checking)\"" + - "./test_rules_scala.sh || buildkite-agent annotate --style 'warning' \"Optional build with last_green Bazel version failed, [see here](${BUILDKITE_BUILD_URL}#${BUILDKITE_JOB_ID}) (It is not mandatory but worth checking)\"" test_rules_scala_macos: name: "./test_rules_scala" platform: macos @@ -104,10 +101,10 @@ tasks: shell_commands: - "./test_cross_build.sh" lint_linux: - name: "bazel //tools:lint_check" + name: "./test_lint.sh" platform: ubuntu2004 - run_targets: - - "//tools:lint_check" + shell_commands: + - "./test_lint.sh" test_rules_scala_jdk21: name: "./test_rules_scala with jdk21" platform: ubuntu2004 diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 000000000..324083a11 --- /dev/null +++ b/.bazelignore @@ -0,0 +1,19 @@ +# Remove once the following is fixed: +# - bazelbuild/bazel: Loading top-level targets in local_path_override modules +# in child directory breaks the build #22208 +# https://github.com/bazelbuild/bazel/issues/22208 +dt_patches/compiler_sources +dt_patches/test_dt_patches +dt_patches/test_dt_patches_user_srcjar +examples/crossbuild +examples/overridden_artifacts +examples/scala3 +examples/semanticdb +examples/testing/multi_frameworks_toolchain +examples/testing/scalatest_repositories +examples/testing/specs2_junit_repositories +test/proto_cross_repo_boundary/repo +test_cross_build +third_party/test/example_external_workspace +third_party/test/new_local_repo +third_party/test/proto diff --git a/.bazelrc b/.bazelrc index da79a94c9..2475b332a 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,8 +1,7 @@ -# Switch to --noenable_workspace when Bzlmod lands. -# https://github.com/bazelbuild/rules_scala/issues/1482 -common --enable_workspace --noenable_bzlmod +# Remove once Bazel 8 becomes the minimum supported version. +common --noenable_workspace --incompatible_use_plus_in_repo_names -# Remove once proto toolchainization becomes the default +# Remove if protocol compiler toolchainization ever becomes the default. # - https://bazel.build/reference/command-line-reference#flag--incompatible_enable_proto_toolchain_resolution # - https://docs.google.com/document/d/1CE6wJHNfKbUPBr7-mmk_0Yo3a4TaqcTPE0OWNuQkhPs/edit common --incompatible_enable_proto_toolchain_resolution diff --git a/.bcr/README.md b/.bcr/README.md new file mode 100644 index 000000000..727d4aa41 --- /dev/null +++ b/.bcr/README.md @@ -0,0 +1,16 @@ +# Bazel Central Registry publication + +The [Publish to BCR GitHub app](https://github.com/bazel-contrib/publish-to-bcr) +uses these configuration files for publishing Bazel modules to the [Bazel +Central Registry (BCR)](https://registry.bazel.build/). + +- [Publish to BCR workflow setup]( + https://github.com/bazel-contrib/publish-to-bcr/tree/main/README.md#setup) +- [.bcr/ templates]( + https://github.com/bazel-contrib/publish-to-bcr/tree/main/templates) + +Related documentation: + +- [bazelbuild/bazel-central-registry]( + https://github.com/bazelbuild/bazel-central-registry) +- [GitHub Actions](https://docs.github.com/actions) diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json new file mode 100644 index 000000000..717a2d23f --- /dev/null +++ b/.bcr/metadata.template.json @@ -0,0 +1,20 @@ +{ + "homepage": "https://github.com/bazelbuild/rules_scala", + "maintainers": [ + { + "name": "Simonas Pinevičius", + "email": "simonas.pinevicius@gmail.com", + "github": "simuons" + }, + { + "name": "Vaidas Pilkauskas", + "email": "vaidas.pilkauskas@gmail.com", + "github": "liucijus" + } + ], + "repository": [ + "github:bazelbuild/rules_scala" + ], + "versions": [], + "yanked_versions": {} +} diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml new file mode 100644 index 000000000..405bdda35 --- /dev/null +++ b/.bcr/presubmit.yml @@ -0,0 +1,14 @@ +bcr_test_module: + module_path: "examples/crossbuild" + matrix: + platform: ["debian10", "macos", "ubuntu2004", "windows"] + bazel: [7.x, 8.x, rolling, last_green] + tasks: + run_tests: + name: "Build and test the example module" + platform: ${{ platform }} + bazel: ${{ bazel }} + build_targets: + - "//..." + test_targets: + - "//..." diff --git a/.bcr/source.template.json b/.bcr/source.template.json new file mode 100644 index 000000000..20374716f --- /dev/null +++ b/.bcr/source.template.json @@ -0,0 +1,5 @@ +{ + "integrity": "", + "strip_prefix": "{REPO}-{VERSION}", + "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz" +} diff --git a/.github/workflows/workspace_snippet.sh b/.github/workflows/workspace_snippet.sh index 1a58f3749..3c86e4249 100755 --- a/.github/workflows/workspace_snippet.sh +++ b/.github/workflows/workspace_snippet.sh @@ -4,13 +4,23 @@ set -o errexit -o nounset -o pipefail # Set by GH actions, see # https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables -TAG=${GITHUB_REF_NAME} -PREFIX="rules_scala-${TAG:1}" +TAG="${GITHUB_REF_NAME}" +VERSION="${TAG:1}" +PREFIX="rules_scala-${VERSION}" ARCHIVE="rules_scala-$TAG.tar.gz" git archive --format=tar --prefix=${PREFIX}/ ${TAG} | gzip > $ARCHIVE SHA=$(shasum -a 256 $ARCHIVE | awk '{print $1}') cat << EOF +## Using Bzlmod + +Paste this snippet into your \`MODULE.bazel\` file: + +\`\`\`starlark +# Set \`repo_name = "io_bazel_rules_scala"\` if you still need it. +bazel_dep(name = "rules_scala", version = "${VERSION}") +\`\`\` + ## Using WORKSPACE Paste this snippet into your \`WORKSPACE\` file: diff --git a/.gitignore b/.gitignore index fdf79752d..ce630e21d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,9 @@ test/semanticdb/tempsrc # From scripts/create_repository.py repository-artifacts.json + +# Until it settles down +**/MODULE.bazel.lock + +# Used by some tests, but can also be used for local experimentation. +tmp/ diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 000000000..e76c2819b --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,286 @@ +"""Bazel module definition for rules_scala""" + +module( + name = "rules_scala", + version = "7.0.0", + bazel_compatibility = [">=7.5.0"], + compatibility_level = 7, +) + +SCALA_VERSION = "2.12.20" + +# These versions match those required by some tests, including +# test_thirdparty_version.sh. +SCALA_2_VERSIONS = [ + "2.11.12", + "2.12.20", + "2.13.16", +] + +SCALA_3_VERSIONS = [ + "3.1.3", + "3.3.5", + "3.5.2", + "3.6.4", +] + +SCALA_VERSIONS = SCALA_2_VERSIONS + SCALA_3_VERSIONS + +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep(name = "abseil-cpp", version = "20250127.1") +bazel_dep(name = "rules_java", version = "8.11.0") +bazel_dep(name = "rules_proto", version = "7.1.0") +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//protoc:0001-protobuf-19679-rm-protoc-dep.patch"], + version = "30.1", +) + +scala_protoc = use_extension( + "//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +scala_config = use_extension( + "//scala/extensions:config.bzl", + "scala_config", +) +use_repo(scala_config, "rules_scala_config") + +dev_config = use_extension( + "//scala/extensions:config.bzl", + "scala_config", + dev_dependency = True, +) +dev_config.settings( + enable_compiler_dependency_tracking = True, + scala_version = SCALA_VERSION, + scala_versions = SCALA_VERSIONS, +) + +scala_deps = use_extension("//scala/extensions:deps.bzl", "scala_deps") +use_repo( + scala_deps, + "rules_scala_toolchains", + "scala_compiler_sources", +) +scala_deps.scala() + +# Register some of our testing toolchains first when building our repo. +register_toolchains( + # This is optional, but still safe to include even when not using + # `--incompatible_enable_proto_toolchain_resolution`. Requires invoking the + # `scala_protoc_toolchains` repo rule. Register this toolchain before any + # others. + "@rules_scala_protoc_toolchains//...:all", + "//scala:unused_dependency_checker_error_toolchain", + "//test/proto:scalapb_toolchain", + "//test/toolchains:java21_toolchain_definition", + dev_dependency = True, +) + +register_toolchains("@rules_scala_toolchains//...:all") + +# Dev dependencies + +dev_deps = use_extension( + "//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +dev_deps.jmh() +dev_deps.junit() +dev_deps.scala_proto() +dev_deps.scalafmt() +dev_deps.scalatest() +dev_deps.specs2() +dev_deps.twitter_scrooge() +use_repo( + dev_deps, + "scala_proto_rules_scalapb_compilerplugin", + "scala_proto_rules_scalapb_protoc_bridge", + "scalafmt_default", +) + +# Default versions of version specific repos needed by some of our tests. Tests +# that set `--repo_env=SCALA_VERSION=...` break without using the default here, +# because version specific repos for other versions won't be available. +use_repo( + dev_deps, + "io_bazel_rules_scala_guava", + "io_bazel_rules_scala_junit_junit", + "io_bazel_rules_scala_scala_compiler", + "io_bazel_rules_scala_scala_library", +) + +[ + [ + use_repo(dev_deps, dep + "_" + scala_version.replace(".", "_")) + for dep in [ + "io_bazel_rules_scala_junit_junit", + "io_bazel_rules_scala_scala_compiler", + "io_bazel_rules_scala_scala_library", + ] + ( + # We can remove this condition once we drop support for Scala 2.11. + [] if scala_version.startswith("2.11.") else [ + "scala_proto_rules_scalapb_protoc_gen", + ] + ) + ] + for scala_version in SCALA_VERSIONS +] + +[ + [ + use_repo(dev_deps, dep + "_" + scala_version.replace(".", "_")) + for dep in [ + "io_bazel_rules_scala_scala_reflect", + ] + ] + for scala_version in SCALA_2_VERSIONS +] + +[ + [ + use_repo(dev_deps, dep + "_" + scala_version.replace(".", "_")) + for dep in [ + "io_bazel_rules_scala_scala_compiler_2", + "io_bazel_rules_scala_scala_library_2", + "io_bazel_rules_scala_scala_reflect_2", + ] + ] + for scala_version in SCALA_3_VERSIONS +] + +internal_dev_deps = use_extension( + "//scala/private/extensions:dev_deps.bzl", + "dev_deps", + dev_dependency = True, +) + +# See //scala/private:extensions/dev_deps.bzl for notes on some of these repos. +use_repo( + internal_dev_deps, + "com_github_bazelbuild_buildtools", + "com_github_jnr_jffi_native", + "com_google_guava_guava_21_0", + "com_google_guava_guava_21_0_with_file", + "com_twitter__scalding_date", + "org_apache_commons_commons_lang_3_5", + "org_apache_commons_commons_lang_3_5_without_file", + "org_springframework_spring_core", + "org_springframework_spring_tx", + "org_typelevel__cats_core", + "org_typelevel_kind_projector", +) + +java_toolchains = use_extension( + "@rules_java//java:extensions.bzl", + "toolchains", + dev_dependency = True, +) +use_repo( + java_toolchains, + # //test/toolchains:java21_toolchain + "remotejdk21_linux", + "remotejdk21_macos", + "remotejdk21_win", + # //test/jmh:test_jmh_jdk8 + "remote_jdk8_linux", + "remote_jdk8_macos", + "remote_jdk8_windows", +) + +[ + ( + bazel_dep(name = name, dev_dependency = True), + local_path_override( + module_name = name, + path = path, + ), + ) + for name, path in [ + ( + "proto_cross_repo_boundary", + "test/proto_cross_repo_boundary/repo", + ), + ( + "test_new_local_repo", + "third_party/test/new_local_repo", + ), + ( + "example_external_workspace", + "third_party/test/example_external_workspace", + ), + ] +] + +bazel_dep( + name = "bazel_ci_rules", + version = "1.0.0", + dev_dependency = True, + repo_name = "bazelci_rules", +) +bazel_dep( + name = "rules_go", + version = "0.53.0", + dev_dependency = True, + repo_name = "io_bazel_rules_go", # for com_github_bazelbuild_buildtools +) +bazel_dep(name = "gazelle", version = "0.42.0", dev_dependency = True) + +go_sdk = use_extension( + "@io_bazel_rules_go//go:extensions.bzl", + "go_sdk", + dev_dependency = True, +) +go_sdk.download(version = "1.24.1") + +go_deps = use_extension( + "@gazelle//:extensions.bzl", + "go_deps", + dev_dependency = True, +) + +# The go_deps.module calls are inspired by the following to get the +# com_github_bazelbuild_buildtools repo to work: +# +# - https://github.com/bazelbuild/bazel-central-registry/blob/main/modules/gazelle/0.39.1/MODULE.bazel#L31-L57 +# +# To get the latest version and hashes for each per: +# +# - https://go.dev/ref/mod#go-list-m +# - https://go.dev/ref/mod#checksum-database +# +# go list -m golang.org/x/tools@latest +# curl https://sum.golang.org/lookup/golang.org/x/tools@v0.31.0 +go_deps.module( + path = "golang.org/x/tools", + sum = "h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=", + version = "v0.31.0", +) +go_deps.module( + path = "github.com/golang/protobuf", + sum = "h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=", + version = "v1.5.4", +) +use_repo( + go_deps, + "com_github_golang_protobuf", + "org_golang_x_tools", +) + +bazel_dep(name = "rules_python", version = "1.2.0", dev_dependency = True) diff --git a/README.md b/README.md index 2d5ff2395..7214d780f 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,118 @@ Bazel version. [Install Bazel]: https://docs.bazel.build/versions/master/install.html [Bazelisk]: https://docs.bazel.build/versions/master/install.html +Add the following configuration snippet to your `MODULE.bazel` file and update +the release `` as specified on the [rules_scala releases +page][releases]. + +[releases]: https://github.com/bazelbuild/rules_scala/releases + +```py +# MODULE.bazel + +# You can add `repo_name = "io_bazel_rules_scala"` if you still need it. +bazel_dep(name = "rules_scala", version = "") + +# Selects the Scala version and other configuration parameters. +# +# 2.12 is the default version. Use other versions by passing them explicitly, as +# illustrated below. +# +# See the documentation of `_settings_attrs` in `scala/extensions/config.bzl` +# for other available parameters. +# +# You may define your own custom toolchain using Maven artifact dependencies +# configured by your `WORKSPACE` file, imported using an external loader like +# https://github.com/bazelbuild/rules_jvm_external. See docs/scala_toolchain.md. +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings(scala_version = "2.13.16") + +# See the `scala/extensions/deps.bzl` docstring for a high level description of +# the tag classes exported by this module extension. +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) + +# Defines a default toolchain repo for the configured Scala version that loads +# Maven deps like the Scala compiler and standard libs. Enable and configure +# other builtin toolchains by instantiating their corresponding tag classes. +# See the documentation in `scala/extensions/deps.bzl` for all builtin +# toolchain configuration options. +# +# On production projects, you may consider defining a custom toolchain to use +# your project's required dependencies instead. In that case, you can omit using +# the module extension and this next line altogether. Or, you can still use the +# module extension to instantiate other optional `rules_scala` toolchains +# without it. +scala_deps.scala() + +# The remaining items are optional, enabling the precompiled protocol compiler +# toolchain via `--incompatible_enable_proto_toolchain_resolution`. See the +# "Using a precompiled protocol compiler" section below. +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +# Register this toolchain before any others in the file. Still safe even when +# `--incompatible_enable_proto_toolchain_resolution` is `False`. +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for protocol compiler toolchainization until resolution +# of protocolbuffers/protobuf#19679. Assumes having copied +# `protoc/0001-protobuf-19679-rm-protoc-dep.patch` from `rules_scala` to +# `protobuf.patch` in the root package. See the "Using a precompiled protocol +# compiler" section below. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) +``` + +### Resolving `protobuf` conflicts + +If a newer `protobuf` version in the module graph breaks your build, use +[`single_version_override`][] or [`multiple_version_override`][] to fix it: + +[`single_version_override`]: https://bazel.build/external/module#single-version_override +[`multiple_version_override`]: https://bazel.build/external/module#multiple-version_override + +```py +bazel_dep( + name = "protobuf", + version = "25.5", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + version = "25.5", +) +``` + +### Legacy `WORKSPACE` configuration + +`rules_scala` 7.x enables existing users to migrate to Bzlmod. `WORKSPACE` +continues to work for Bazel [6.5.0 (for now)](#6.5.0), 7.6.0, and 8, but +[__`WORKSPACE` is going away in Bazel 9__][bazel-9]. + +[bazel-9]: https://bazel.build/external/migration + Add the following configuration snippet to your `WORKSPACE` file and update the release `` and its `` as specified on the [rules_scala releases page][releases]. This snippet is designed to ensure that users pick up the @@ -46,8 +158,6 @@ correct order of dependencies for `rules_scala`. If you want to override any of the following dependency versions, make sure to `load()` them before calling `rules_scala_dependencies()`. -[releases]: https://github.com/bazelbuild/rules_scala/releases - ```py # WORKSPACE load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") @@ -297,10 +407,8 @@ the remote execution platform is Linux running on an x86 processor. ```py # MODULE.bazel -scala_protoc_toolchains( - name = "rules_scala_protoc_toolchains", +scala_protoc.toolchains( platforms = ["linux-x86_64"], - dev_dependency = True, ) ``` @@ -335,7 +443,6 @@ which also requires that the patch be a regular file in your own repo. In other words, neither `@rules_scala//protoc:0001-protobuf-19679-rm-protoc-dep.patch` nor an [`alias`][] to it will work. -[`single_version_override`]: https://bazel.build/rules/lib/globals/module#single_version_override [`alias`]: https://bazel.build/reference/be/general#alias Assuming you've copied the patch to a file called `protobuf.patch` in the root @@ -530,7 +637,7 @@ The Bazel versions and dependency versions in the table below represent the maximum available at the time of writing. - For the actual versions used by `rules_scala`, see - [scala/deps.bzl](scala/deps.bzl). + [MODULE.bazel](./MODULE.bazel). - See [.bazelci/presubmit.yml](./.bazelci/presubmit.yml) for the exact Bazel versions verified by the continuous integration builds. @@ -812,14 +919,26 @@ same exact call will also work in `MODULE.bazel`. When [using 'setup_scala_toolchain()' with custom compiler JARs]( docs/scala_toolchain.md#b-defining-your-own-scala_toolchain), don't use -`scala_toolchains()` if you don't need any other builtin toolchains. +`scala_deps` or `scala_toolchains()` if you don't need any other builtin +toolchains. -If you do need other builtin toolchains, then set `scala = False`: +If you do need other builtin toolchains when using Bzlmod, then use the module +extension and only instantiate the tag classes to the corresponding toolchains. + +If you do need other builtin toolchains when using `WORKSPACE`, then set `scala += False`. ```py +# MODULE.bazel +scala_deps.scala_proto() +scala_deps.twitter_scrooge() +# ...other scala_deps tag class instantations... + # WORKSPACE scala_toolchains( scala = False, + scala_proto = True, + twitter_scrooge = True, # ...other toolchain parameters... ) ``` @@ -840,12 +959,11 @@ See ["Defining your own scala_toolchain > Step 3 (optional)" from docs/scala_toolchain.md](docs/scala_toolchain.md#step-3-optional) for futher details. -### Bzlmod configuration (coming soon!) +### Bzlmod configuration -The upcoming Bzlmod implementation will funnel through the `scala_toolchains()` -macro as well, ensuring maximum compatibility with `WORKSPACE` configurations. -The equivalent Bzlmod configuration for the `scala_toolchains()` configuration -above would be: +The Bzlmod implementation funnels through the `scala_toolchains()` macro as +well, ensuring maximum compatibility with `WORKSPACE` configurations. The +equivalent Bzlmod snippet for the `scala_toolchains()` snippet above would be: ```py bazel_dep(name = "rules_scala", version = "7.0.0") @@ -860,13 +978,12 @@ scala_deps = use_extension( "@rules_scala//scala/extensions:deps.bzl", "scala_deps", ) -scala_deps.toolchains( - scalafmt = True, - scalatest = True, -) +scala_deps.scala() +scala_deps.scalafmt() +scala_deps.scalatest() ``` -The module extensions will call `scala_config()` and `scala_toolchains()` +The module extensions call `scala_config()` and `scala_toolchains()` respectively. The `MODULE.bazel` file for `rules_scala` declares its own dependencies via `bazel_dep()`, allowing Bazel to resolve versions according to the main repository/root module configuration. It also calls @@ -875,12 +992,11 @@ register a specific toolchain to resolve first). [reg_tool]: https://bazel.build/rules/lib/globals/module#register_toolchains -The `MODULE.bazel` files in this repository will also provide many examples -(when they land per bazelbuild/rules_scala#1482). +The `MODULE.bazel` files in this repository provide many examples. #### Copy `register_toolchains()` calls from `WORKSPACE` to `MODULE.bazel` -The `MODULE.bazel` file from `rules_scala` will automatically call +The `MODULE.bazel` file from `rules_scala` automatically calls `register_toolchains()` for toolchains configured via its `scala_deps` module extension. However, you must register explicitly in your `MODULE.bazel` file any toolchains that you want to take precedence over the toolchains configured by @@ -1273,9 +1389,7 @@ To access these repositories under Bzlmod, you'll need to add the following to your `MODULE.bazel` file: ```py -scala_deps.toolchains( - twitter_scrooge = True, -) +scala_deps.twitter_scrooge() use_repo( scala_deps, "io_bazel_rules_scala_guava", diff --git a/docs/cross-compilation.md b/docs/cross-compilation.md index 1fbcbef75..144fcb541 100644 --- a/docs/cross-compilation.md +++ b/docs/cross-compilation.md @@ -22,7 +22,7 @@ scala_config = use_extension( ) scala_config.settings( - scala_version = "2.13.15", + scala_version = "2.13.16", # No need to include `scala_version` in `scala_versions`. scala_versions = [ "2.11.12", @@ -45,7 +45,7 @@ scala_config( scala_versions = [ "2.11.12", "2.12.20", - "2.13.15", + "2.13.16", "3.1.3", "3.2.2", "3.3.5", diff --git a/docs/scala_toolchain.md b/docs/scala_toolchain.md index bfe1c9b3d..a30460f43 100644 --- a/docs/scala_toolchain.md +++ b/docs/scala_toolchain.md @@ -104,26 +104,27 @@ register_toolchains("//toolchains:my_scala_toolchain") #### Step 3 (optional) When using your own JARs for every `setup_scala_toolchain()` argument, while -using `scala_toolchains()` to instantiate other builtin toolchains, set `scala = -False`: +using `scala_deps` or`scala_toolchains()` to instantiate other builtin +toolchains: -```py -# WORKSPACE -scala_toolchains( - scala = False, - # ...other toolchain parameters... -) -``` +- Bzlmod: Don't instantiate `scala_deps.scala()`. +- `WORKSPACE`: Call `scala_toolchains(scala = False, ...)`. -Otherwise, `scala_toolchains()` will try to instantiate a default Scala -toolchain and its compiler JAR repositories. The build will then fail if the -configured Scala version doesn't match the `scala_version` value in the -corresponding `third_party/repositories/scala_*.bzl` file. +Otherwise, `scala_deps` or `scala_toolchains()` will try to instantiate a +default Scala toolchain and its compiler JAR repositories. The build will then +fail if the configured Scala version doesn't match the `scala_version` value in +the corresponding `third_party/repositories/scala_*.bzl` file. If you don't specify your own jars for every `setup_scala_toolchain()` argument, set `validate_scala_version = False` to disable the Scala version check. ```py +# MODULE.bazel +scala_deps.settings( + validate_scala_version = False, + # ...other toolchain parameters... +) + # WORKSPACE scala_toolchains( validate_scala_version = False, diff --git a/dt_patches/compiler_sources/MODULE.bazel b/dt_patches/compiler_sources/MODULE.bazel new file mode 100644 index 000000000..0ef3ed047 --- /dev/null +++ b/dt_patches/compiler_sources/MODULE.bazel @@ -0,0 +1,15 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "compiler_sources") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +use_repo(scala_config, "rules_scala_config") diff --git a/dt_patches/compiler_sources/extensions.bzl b/dt_patches/compiler_sources/extensions.bzl index ac88e6591..6aec4a5f6 100644 --- a/dt_patches/compiler_sources/extensions.bzl +++ b/dt_patches/compiler_sources/extensions.bzl @@ -61,3 +61,10 @@ def import_compiler_source_repos(): licenses = ["notice"], server_urls = default_maven_server_urls(), ) + +def _compiler_source_repos_impl(_ctx): + import_compiler_source_repos() + +compiler_source_repos = module_extension( + implementation = _compiler_source_repos_impl, +) diff --git a/dt_patches/test_dt_patches/BUILD b/dt_patches/test_dt_patches/BUILD index a3726aea1..4110341ba 100644 --- a/dt_patches/test_dt_patches/BUILD +++ b/dt_patches/test_dt_patches/BUILD @@ -17,7 +17,9 @@ SCALA_LIBS = ["@scala_library"] + select_for_scala_version( setup_scala_toolchain( name = "dt_scala_toolchain", + parser_combinators_deps = [], scala_compile_classpath = ["@scala_compiler"] + SCALA_LIBS, scala_library_classpath = SCALA_LIBS, scala_macro_classpath = SCALA_LIBS, + scala_xml_deps = [], ) diff --git a/dt_patches/test_dt_patches/MODULE.bazel b/dt_patches/test_dt_patches/MODULE.bazel new file mode 100644 index 000000000..19e2633e9 --- /dev/null +++ b/dt_patches/test_dt_patches/MODULE.bazel @@ -0,0 +1,81 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "scala3_example") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + enable_compiler_dependency_tracking = True, +) +use_repo(scala_config, "rules_scala_config") + +bazel_dep(name = "compiler_sources") +local_path_override( + module_name = "compiler_sources", + path = "../compiler_sources", +) + +source_repos = use_extension( + "@compiler_sources//:extensions.bzl", + "compiler_source_repos", +) +use_repo( + source_repos, + # Configured for the current Scala version + "scala_compiler", + "scala_library", + # Scala 2 specific + "scala_reflect", + # Scala 3 specific + "scala3_interfaces", + "tasty_core", + # Hardcoded versions + "sbt_compiler_interface", + "scala2_library", + "scala_asm", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, + validate_scala_version = False, +) +scala_deps.scala() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + "//:dt_scala_toolchain", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/dt_patches/test_dt_patches/protobuf.patch b/dt_patches/test_dt_patches/protobuf.patch new file mode 120000 index 000000000..c8f00be6e --- /dev/null +++ b/dt_patches/test_dt_patches/protobuf.patch @@ -0,0 +1 @@ +../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/dt_patches/test_dt_patches_user_srcjar/BUILD b/dt_patches/test_dt_patches_user_srcjar/BUILD index a5844ba9b..f3345ba4d 100644 --- a/dt_patches/test_dt_patches_user_srcjar/BUILD +++ b/dt_patches/test_dt_patches_user_srcjar/BUILD @@ -20,7 +20,9 @@ SCALA_LIBS = ["@scala_library"] + select_for_scala_version( setup_scala_toolchain( name = "dt_scala_toolchain", + parser_combinators_deps = [], scala_compile_classpath = ["@scala_compiler"] + SCALA_LIBS, scala_library_classpath = SCALA_LIBS, scala_macro_classpath = SCALA_LIBS, + scala_xml_deps = [], ) diff --git a/dt_patches/test_dt_patches_user_srcjar/MODULE.bazel b/dt_patches/test_dt_patches_user_srcjar/MODULE.bazel new file mode 100644 index 000000000..ee8791129 --- /dev/null +++ b/dt_patches/test_dt_patches_user_srcjar/MODULE.bazel @@ -0,0 +1,193 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "scala3_example") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + enable_compiler_dependency_tracking = True, +) +use_repo(scala_config, "rules_scala_config") + +bazel_dep(name = "compiler_sources") +local_path_override( + module_name = "compiler_sources", + path = "../compiler_sources", +) + +source_repos = use_extension( + "@compiler_sources//:extensions.bzl", + "compiler_source_repos", +) +use_repo( + source_repos, + # Configured for the current Scala version + "scala_compiler", + "scala_library", + # Scala 2 specific + "scala_reflect", + # Scala 3 specific + "scala3_interfaces", + "tasty_core", + # Hardcoded versions + "sbt_compiler_interface", + "scala2_library", + "scala_asm", +) + +srcjar_repos = use_extension( + "//:extensions.bzl", + "compiler_user_srcjar_repos", +) +use_repo( + srcjar_repos, + "scala3_compiler_srcjar", + "scala_compiler_srcjar", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, + validate_scala_version = False, +) +scala_deps.scala() + +# The `scala_deps.compiler_srcjar()` tag prevents some of the kinds of errors +# represented in the corresponding `WORKSPACE` file, so we have to force +# different ones. In particular, we can't use unspecified data types or kwargs, +# or Bazel itself will error out. + +# Invalid +scala_deps.compiler_srcjar( + url = "foo", + urls = ["bar"], + version = "2.12.11", +) + +# Invalid +scala_deps.compiler_srcjar( + label = "baz", + url = "foo", + version = "2.12.12", +) + +# Invalid +scala_deps.compiler_srcjar( + label = "baz", + urls = ["bar"], + version = "2.12.13", +) +scala_deps.compiler_srcjar( + integrity = "sha384-yKJTudaHM2dA+VM//elLxhEfOmyCYRHzbLlQcf5jlrR+G5FEW+fBW/b794mQLMOX", + urls = ["https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.12.14/scala-compiler-2.12.14-sources.jar"], + version = "2.12.14", +) +scala_deps.compiler_srcjar( + sha256 = "65f783f1fbef7de661224f607ac07ca03c5d19acfdb7f2234ff8def1e79b5cd8", + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.12.15/scala-compiler-2.12.15-sources.jar", + version = "2.12.15", +) +scala_deps.compiler_srcjar( + label = "@scala_compiler_srcjar//jar:downloaded.jar", + version = "2.12.16", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.12.17/scala-compiler-2.12.17-sources.jar?foo", + version = "2.12.17", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.12.18/scala-compiler-2.12.18-sources.jar?foo", + version = "2.12.18", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.12.19/scala-compiler-2.12.19-sources.jar?foo", + version = "2.12.19", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.12.20/scala-compiler-2.12.20-sources.jar?foo", + version = "2.12.20", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.13.11/scala-compiler-2.13.11-sources.jar?foo", + version = "2.13.11", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.13.12/scala-compiler-2.13.12-sources.jar?foo", + version = "2.13.12", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.13.14/scala-compiler-2.13.14-sources.jar?foo", + version = "2.13.14", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.13.15/scala-compiler-2.13.15-sources.jar?foo", + version = "2.13.15", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.13.16/scala-compiler-2.13.16-sources.jar?foo", + version = "2.13.16", +) +scala_deps.compiler_srcjar( + integrity = "sha384-4J2ihR1QSdP5cvL3y2OUfw4uUX/hsQqcPlJV+IrQdsM/soiIAYfoEeIEt6vl3xBk", + url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.1.3/scala3-compiler_3-3.1.3-sources.jar", + version = "3.1.3", +) +scala_deps.compiler_srcjar( + sha256 = "669d580fc4a8d3c2e2d13d5735ae9be05d567613fe44482de5bcc5e2e2ee89ea", + url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.2.2/scala3-compiler_3-3.2.2-sources.jar", + version = "3.2.2", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.3.5/scala3-compiler_3-3.3.5-sources.jar", + version = "3.3.5", +) +scala_deps.compiler_srcjar( + label = "@scala3_compiler_srcjar//jar:downloaded.jar", + version = "3.4.3", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.5.2/scala3-compiler_3-3.5.2-sources.jar", + version = "3.5.2", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.6.4/scala3-compiler_3-3.6.4-sources.jar", + version = "3.6.4", +) + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + "//:dt_scala_toolchain", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/dt_patches/test_dt_patches_user_srcjar/protobuf.patch b/dt_patches/test_dt_patches_user_srcjar/protobuf.patch new file mode 120000 index 000000000..c8f00be6e --- /dev/null +++ b/dt_patches/test_dt_patches_user_srcjar/protobuf.patch @@ -0,0 +1 @@ +../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/examples/crossbuild/BUILD b/examples/crossbuild/BUILD new file mode 100644 index 000000000..e69de29bb diff --git a/examples/crossbuild/MODULE.bazel b/examples/crossbuild/MODULE.bazel new file mode 100644 index 000000000..07b6ab4b2 --- /dev/null +++ b/examples/crossbuild/MODULE.bazel @@ -0,0 +1,60 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "cross_build") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + scala_version = "3.3.5", + scala_versions = [ + "2.11.12", + "2.13.16", + "3.3.5", + ], +) +use_repo(scala_config, "rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() +scala_deps.scalatest() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/examples/crossbuild/protobuf.patch b/examples/crossbuild/protobuf.patch new file mode 120000 index 000000000..c8f00be6e --- /dev/null +++ b/examples/crossbuild/protobuf.patch @@ -0,0 +1 @@ +../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/examples/overridden_artifacts/MODULE.bazel b/examples/overridden_artifacts/MODULE.bazel new file mode 100644 index 000000000..4a4b466ba --- /dev/null +++ b/examples/overridden_artifacts/MODULE.bazel @@ -0,0 +1,85 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "overridden_artifacts") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + scala_version = "3.3.5", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.settings( + fetch_sources = False, +) +scala_deps.scala() + +# Deliberately set for Scala 3.3 and 2.13 versions less than the most +# recently supported. See the `scala_version` setting at the top of +# `third_party/repositories/scala_{2_13,3_3}.bzl`. +scala_deps.overridden_artifact( + name = "io_bazel_rules_scala_scala_library", + artifact = "org.scala-lang:scala3-library_3:3.3.4", + sha256 = "d95184acfcd814da2e051378e4962c653f4b468f4086452ab427af030482bd3c", +) +scala_deps.overridden_artifact( + name = "io_bazel_rules_scala_scala_compiler", + artifact = "org.scala-lang:scala3-compiler_3:3.3.4", + sha256 = "2cca65fdb92e2cc393786cae61b4f7bcb9032ad4be61f9cebae1dca72997e52f", + # These are _not_ strictly required in this case, but we want to test that + # nothing breaks when they're specified. + deps = [ + "@io_bazel_rules_scala_scala_asm", + "@io_bazel_rules_scala_scala_interfaces", + "@io_bazel_rules_scala_scala_library", + "@io_bazel_rules_scala_scala_tasty_core", + "@org_jline_jline_reader", + "@org_jline_jline_terminal", + "@org_jline_jline_terminal_jni", + "@org_scala_sbt_compiler_interface", + ], +) +scala_deps.overridden_artifact( + name = "io_bazel_rules_scala_scala_library_2", + artifact = "org.scala-lang:scala-library:2.13.14", + sha256 = "43e0ca1583df1966eaf02f0fbddcfb3784b995dd06bfc907209347758ce4b7e3", +) +scala_deps.scalatest() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/examples/overridden_artifacts/protobuf.patch b/examples/overridden_artifacts/protobuf.patch new file mode 120000 index 000000000..c8f00be6e --- /dev/null +++ b/examples/overridden_artifacts/protobuf.patch @@ -0,0 +1 @@ +../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/examples/scala3/MODULE.bazel b/examples/scala3/MODULE.bazel new file mode 100644 index 000000000..4670f4e31 --- /dev/null +++ b/examples/scala3/MODULE.bazel @@ -0,0 +1,44 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "scala3_example") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/examples/scala3/protobuf.patch b/examples/scala3/protobuf.patch new file mode 120000 index 000000000..c8f00be6e --- /dev/null +++ b/examples/scala3/protobuf.patch @@ -0,0 +1 @@ +../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/examples/semanticdb/MODULE.bazel b/examples/semanticdb/MODULE.bazel new file mode 100644 index 000000000..f67f8e214 --- /dev/null +++ b/examples/semanticdb/MODULE.bazel @@ -0,0 +1,54 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "semanticdb_example") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + scala_version = "2.13.16", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +# Register and use the custom toolchain that has semanticdb enabled +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + "//:semanticdb_toolchain", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/examples/semanticdb/protobuf.patch b/examples/semanticdb/protobuf.patch new file mode 120000 index 000000000..c8f00be6e --- /dev/null +++ b/examples/semanticdb/protobuf.patch @@ -0,0 +1 @@ +../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/examples/testing/multi_frameworks_toolchain/MODULE.bazel b/examples/testing/multi_frameworks_toolchain/MODULE.bazel new file mode 100644 index 000000000..b5a104943 --- /dev/null +++ b/examples/testing/multi_frameworks_toolchain/MODULE.bazel @@ -0,0 +1,94 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "multi_frameworks_toolchain") + +SCALA_VERSION = "2.12.20" + +VERSION_SUFFIX = "_" + SCALA_VERSION.replace(".", "_") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + scala_version = SCALA_VERSION, +) +use_repo(scala_config, "rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() +scala_deps.junit() +scala_deps.scalatest() +scala_deps.specs2() + +# Under normal circumstances, the above `scala_deps` tag class instantiations +# would be all you need. rules_scala will set up and register the toolchains +# automatically. +# +# However, we need to import the repos used by the +# `setup_scala_testing_toolchain()` example in the `BUILD` file. These repos +# are versioned by Scala version, so we have to append the `VERSION_SUFFIX`. +[ + use_repo(scala_deps, "io_bazel_rules_scala_" + dep + VERSION_SUFFIX) + for dep in [ + "junit_junit", + "org_hamcrest_hamcrest_core", + "scalactic", + "scalatest", + "scalatest_compatible", + "scalatest_core", + "scalatest_featurespec", + "scalatest_flatspec", + "scalatest_freespec", + "scalatest_funspec", + "scalatest_funsuite", + "scalatest_matchers_core", + "scalatest_mustmatchers", + "scalatest_shouldmatchers", + "org_specs2_specs2_common", + "org_specs2_specs2_core", + "org_specs2_specs2_fp", + "org_specs2_specs2_matcher", + "org_specs2_specs2_junit", + ] +] + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + "//:testing_toolchain", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/examples/testing/multi_frameworks_toolchain/protobuf.patch b/examples/testing/multi_frameworks_toolchain/protobuf.patch new file mode 120000 index 000000000..0c9d2569d --- /dev/null +++ b/examples/testing/multi_frameworks_toolchain/protobuf.patch @@ -0,0 +1 @@ +../../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/examples/testing/scalatest_repositories/MODULE.bazel b/examples/testing/scalatest_repositories/MODULE.bazel new file mode 100644 index 000000000..556dd858c --- /dev/null +++ b/examples/testing/scalatest_repositories/MODULE.bazel @@ -0,0 +1,46 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "scalatest_repositories") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../../..", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() +scala_deps.scalatest() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/examples/testing/scalatest_repositories/protobuf.patch b/examples/testing/scalatest_repositories/protobuf.patch new file mode 120000 index 000000000..0c9d2569d --- /dev/null +++ b/examples/testing/scalatest_repositories/protobuf.patch @@ -0,0 +1 @@ +../../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/examples/testing/specs2_junit_repositories/MODULE.bazel b/examples/testing/specs2_junit_repositories/MODULE.bazel new file mode 100644 index 000000000..43ad734c7 --- /dev/null +++ b/examples/testing/specs2_junit_repositories/MODULE.bazel @@ -0,0 +1,46 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "specs2_junit_repositories") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../../..", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() +scala_deps.specs2() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/examples/testing/specs2_junit_repositories/protobuf.patch b/examples/testing/specs2_junit_repositories/protobuf.patch new file mode 120000 index 000000000..0c9d2569d --- /dev/null +++ b/examples/testing/specs2_junit_repositories/protobuf.patch @@ -0,0 +1 @@ +../../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/scala/extensions/BUILD b/scala/extensions/BUILD new file mode 100644 index 000000000..e69de29bb diff --git a/scala/extensions/config.bzl b/scala/extensions/config.bzl new file mode 100644 index 000000000..746a6ea74 --- /dev/null +++ b/scala/extensions/config.bzl @@ -0,0 +1,81 @@ +"""Core `rules_scala` configuration + +Provides the `scala_config` module extension with the `settings` tag class. +See the `_settings_attrs` dict for documentation. +""" + +load( + "//scala/private:macros/bzlmod.bzl", + "root_module_tags", + "single_tag_values", +) +load( + "//:scala_config.bzl", + "DEFAULT_SCALA_VERSION", + _scala_config = "scala_config", +) + +_settings_defaults = { + "scala_version": DEFAULT_SCALA_VERSION, + "scala_versions": [], + "enable_compiler_dependency_tracking": False, +} + +_settings_attrs = { + "scala_version": attr.string( + default = _settings_defaults["scala_version"], + doc = ( + "Scala version used by the default toolchain. " + + "Overridden by the `SCALA_VERSION` environment variable." + ), + ), + "scala_versions": attr.string_list( + default = _settings_defaults["scala_versions"], + doc = ( + "Other Scala versions used in cross build targets " + + "(specified by the `scala_version` attribute of `scala_*` rules)" + ), + ), + "enable_compiler_dependency_tracking": attr.bool( + default = _settings_defaults["enable_compiler_dependency_tracking"], + doc = ( + "Enables `scala_toolchain` dependency tracking features. " + + "Overridden by the `ENABLE_COMPILER_DEPENDENCY_TRACKING` " + + "environment variable." + ), + ), +} + +_tag_classes = { + "settings": tag_class( + attrs = _settings_attrs, + doc = "Core `rules_scala` parameters", + ), +} + +def _scala_config_impl(module_ctx): + tags = root_module_tags(module_ctx, _tag_classes.keys()) + settings = single_tag_values(module_ctx, tags.settings, _settings_defaults) + + menv = module_ctx.os.environ + version = menv.get("SCALA_VERSION", settings["scala_version"]) + versions = {version: None} | {v: None for v in settings["scala_versions"]} + + _scala_config( + scala_version = version, + scala_versions = versions.keys(), + enable_compiler_dependency_tracking = menv.get( + "ENABLE_COMPILER_DEPENDENCY_TRACKING", + settings["enable_compiler_dependency_tracking"], + ), + ) + +scala_config = module_extension( + implementation = _scala_config_impl, + tag_classes = _tag_classes, + environ = ["SCALA_VERSION", "ENABLE_COMPILER_DEPENDENCY_TRACKING"], + doc = ( + "Configures core `rules_scala` parameters and exports them via the " + + "`@rules_scala_config` repository" + ), +) diff --git a/scala/extensions/deps.bzl b/scala/extensions/deps.bzl new file mode 100644 index 000000000..5195f3d47 --- /dev/null +++ b/scala/extensions/deps.bzl @@ -0,0 +1,272 @@ +"""Configures builtin toolchains. + +Provides the `scala_deps` module extension with the following tag classes: + +- `settings` +- `overridden_artifact` +- `compiler_srcjar` +- `scala` +- `scalatest` +- `junit` +- `specs2` +- `scalafmt` +- `scala_proto` +- `twitter_scrooge` +- `jmh` + +For documentation, see the `_tag_classes` dict, and the `__attrs` dict +corresponding to each `` listed above. + +See the `scala/private/macros/bzlmod.bzl` docstring for a description of +the defaults, attrs, and tag class dictionaries pattern employed here. +""" + +load( + "//scala/private:macros/bzlmod.bzl", + "repeated_tag_values", + "root_module_tags", + "single_tag_values", +) +load("//scala:scala_cross_version.bzl", "default_maven_server_urls") +load("//scala:toolchains.bzl", "scala_toolchains") + +_settings_defaults = { + "maven_servers": default_maven_server_urls(), + "fetch_sources": True, + "validate_scala_version": True, +} + +_settings_attrs = { + "maven_servers": attr.string_list( + default = _settings_defaults["maven_servers"], + doc = "Maven servers used to fetch dependency jar files", + ), + "fetch_sources": attr.bool( + default = _settings_defaults["fetch_sources"], + doc = "Download dependency source jars", + ), + "validate_scala_version": attr.bool( + default = _settings_defaults["validate_scala_version"], + doc = ( + "Check if the configured Scala version matches " + + "the default version supported by rules_scala. " + + "Only takes effect when the builtin Scala toolchain is " + + "instantiated via `scala_deps.scala()`." + ), + ), +} + +_overridden_artifact_attrs = { + "name": attr.string( + doc = ( + "Repository name of artifact to override from " + + "`third_party/repositories/scala_*.bzl`" + ), + mandatory = True, + ), + "artifact": attr.string( + doc = "Maven coordinates of the overriding artifact", + mandatory = True, + ), + "sha256": attr.string( + doc = "SHA256 checksum of the `artifact`", + mandatory = True, + ), + "deps": attr.string_list( + doc = ( + "Repository names of artifact dependencies (with leading `@`), " + + "if required" + ), + ), +} + +_compiler_srcjar_attrs = { + "version": attr.string(mandatory = True), + "url": attr.string(), + "urls": attr.string_list(), + "label": attr.label(), + "sha256": attr.string(), + "integrity": attr.string(), +} + +_scalafmt_defaults = { + "default_config_path": ".scalafmt.conf", +} + +_scalafmt_attrs = { + "default_config_path": attr.string( + default = _scalafmt_defaults["default_config_path"], + doc = ( + "The relative path to the default Scalafmt config file " + + "within the repository" + ), + ), +} + +_scala_proto_defaults = { + "options": [], +} + +_scala_proto_attrs = { + "options": attr.string_list( + default = _scala_proto_defaults["options"], + doc = "Protobuf options, like 'scala3_sources' or 'grpc'", + ), +} + +_twitter_scrooge_defaults = { + "libthrift": None, + "scrooge_core": None, + "scrooge_generator": None, + "util_core": None, + "util_logging": None, +} + +_twitter_scrooge_attrs = { + k: attr.label(default = v) + for k, v in _twitter_scrooge_defaults.items() +} + +# Tag classes affecting all toolchains. +_general_tag_classes = { + "settings": tag_class( + attrs = _settings_attrs, + doc = "Settings affecting the configuration of all toolchains", + ), + "overridden_artifact": tag_class( + attrs = _overridden_artifact_attrs, + doc = """ +Artifacts overriding the defaults for the configured Scala version. + +Can be specified multiple times, but each `name` must be unique. The default +artifacts are defined by the `third_party/repositories/scala_*.bzl` file +matching the Scala version. +""", + ), + "compiler_srcjar": tag_class( + attrs = _compiler_srcjar_attrs, + doc = """ +Metadata for locating compiler source jars. + +Can be specified multiple times, but each `version` must be unique. Each +instance must contain: + + - `version` + - exactly one of `label`, `url`, or `urls` + - `integrity` or `sha256` are optional, but highly recommended +""", + ), +} + +# Tag classes for supported toolchains. +_toolchain_tag_classes = { + "scala": tag_class( + doc = "Configures the Scala toolchain", + ), + "scalatest": tag_class( + doc = "Configures the ScalaTest", + ), + "junit": tag_class( + doc = "Configures the JUnit toolchain", + ), + "specs2": tag_class( + doc = "Configures the Specs2 toolchain", + ), + "scalafmt": tag_class( + attrs = _scalafmt_attrs, + doc = "Configures the Scalafmt toolchain", + ), + "scala_proto": tag_class( + attrs = _scala_proto_attrs, + doc = "Configures the scala_proto toolchain", + ), + "twitter_scrooge": tag_class( + attrs = _twitter_scrooge_attrs, + doc = "Configures the twitter_scrooge toolchain", + ), + "jmh": tag_class( + doc = "Configures the Java Microbenchmark Harness", + ), +} + +_tag_classes = _general_tag_classes | _toolchain_tag_classes + +def _toolchains(mctx): + result = {k: False for k in _toolchain_tag_classes} + + for mod in mctx.modules: + values = {tc: len(getattr(mod.tags, tc)) != 0 for tc in result} + + if mod.is_root: + return values + + # Don't overwrite `True` values with `False` from another tag. + result.update({k: v for k, v in values.items() if v}) + + return result + +def _scala_proto_options(mctx): + result = {} + + for mod in mctx.modules: + for tag in mod.tags.scala_proto: + result.update({opt: True for opt in tag.options}) + + return sorted(result.keys()) + +def _scala_deps_impl(module_ctx): + tags = root_module_tags(module_ctx, _tag_classes.keys()) + scalafmt = single_tag_values(module_ctx, tags.scalafmt, _scalafmt_defaults) + scrooge_deps = single_tag_values( + module_ctx, + tags.twitter_scrooge, + _twitter_scrooge_defaults, + ) + + scala_toolchains( + overridden_artifacts = repeated_tag_values( + tags.overridden_artifact, + _overridden_artifact_attrs, + ), + scala_compiler_srcjars = repeated_tag_values( + tags.compiler_srcjar, + _compiler_srcjar_attrs, + ), + scala_proto_options = _scala_proto_options(module_ctx), + # `None` breaks the `attr.string_dict` in `scala_toolchains_repo`. + twitter_scrooge_deps = {k: v for k, v in scrooge_deps.items() if v}, + **( + single_tag_values(module_ctx, tags.settings, _settings_defaults) | + {"scalafmt_%s" % k: v for k, v in scalafmt.items()} | + _toolchains(module_ctx) + ) + ) + +scala_deps = module_extension( + implementation = _scala_deps_impl, + tag_classes = _tag_classes, + doc = """Selects and configures builtin toolchains. + +If the root module explicitly uses the extension, it assumes responsibility for +selecting all required toolchains by insantiating the corresponding tag classes: + +```py +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.scala() +scala_deps.scala_proto() + +dev_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +dev_deps.scalafmt() +dev_deps.scalatest() + +# And so on... +``` +""", +) diff --git a/scala/extensions/protoc.bzl b/scala/extensions/protoc.bzl new file mode 100644 index 000000000..ba4746d6a --- /dev/null +++ b/scala/extensions/protoc.bzl @@ -0,0 +1,88 @@ +"""Precompiled protocol compiler toolchains configuration + +Usage: + +```py +# MODULE.bazel + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +# If you need additional platforms: +scala_protoc.toolchains( + platforms = ["linux-x86_64"], +) + +# Register this toolchain before any others. +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) +``` + +For documentation, see the `_tag_classes` dict, and the `__attrs` dict +corresponding to each `` listed above. + +See the `scala/private/macros/bzlmod.bzl` docstring for a description of +the defaults, attrs, and tag class dictionaries pattern employed here. +""" + +load("//protoc:private/protoc_toolchains.bzl", "scala_protoc_toolchains") +load( + "//scala/private:macros/bzlmod.bzl", + "root_module_tags", + "single_tag_values", +) + +_TOOLCHAINS_REPO = "rules_scala_protoc_toolchains" + +_toolchains_defaults = { + "platforms": [], +} + +_toolchains_attrs = { + "platforms": attr.string_list( + default = _toolchains_defaults["platforms"], + doc = ( + "Operating system and architecture identifiers for " + + "precompiled protocol compiler releases, taken from " + + "protocolbuffers/protobuf releases file name suffixes. If " + + "unspecified, will use the identifier matching the " + + "`HOST_CONSTRAINTS` from `@platforms//host:constraints.bzl`." + + " Only takes effect when" + + "`--incompatible_enable_proto_toolchain_resolution` is " + + "`True`." + ), + ), +} + +_tag_classes = { + "toolchains": tag_class( + attrs = _toolchains_attrs, + doc = "Precompiled compiler toolchain options", + ), +} + +def _scala_protoc_impl(module_ctx): + if module_ctx.root_module_has_non_dev_dependency: + fail("scala_protoc must be a dev_dependency") + + tags = root_module_tags(module_ctx, _tag_classes.keys()) + scala_protoc_toolchains( + name = _TOOLCHAINS_REPO, + **single_tag_values(module_ctx, tags.toolchains, _toolchains_defaults) + ) + return module_ctx.extension_metadata( + root_module_direct_deps = [], + root_module_direct_dev_deps = [_TOOLCHAINS_REPO], + ) + +scala_protoc = module_extension( + implementation = _scala_protoc_impl, + tag_classes = _tag_classes, + doc = "Configures precompiled protocol compiler toolchains", +) diff --git a/scala/private/extensions/dev_deps.bzl b/scala/private/extensions/dev_deps.bzl index 29c4fd52c..db15fcdd2 100644 --- a/scala/private/extensions/dev_deps.bzl +++ b/scala/private/extensions/dev_deps.bzl @@ -1,5 +1,10 @@ """Repositories for testing rules_scala itself""" +load( + "//scala/private:macros/bzlmod.bzl", + "root_module_tags", + "single_tag_values", +) load("//scala:scala_cross_version.bzl", "default_maven_server_urls") load("//scala:scala_maven_import_external.bzl", "java_import_external") load("//third_party/repositories:repositories.bzl", "repositories") @@ -7,10 +12,28 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") _BUILD_TOOLS_RELEASE = "5.1.0" +_settings_defaults = { + "maven_servers": default_maven_server_urls(), + "fetch_sources": False, +} + +_settings_attrs = { + "maven_servers": attr.string_list( + default = _settings_defaults["maven_servers"], + ), + "fetch_sources": attr.bool( + default = _settings_defaults["fetch_sources"], + ), +} + +_tag_classes = { + "settings": tag_class(attrs = _settings_attrs), +} + def dev_deps_repositories( name = "unused_dev_deps_name", - maven_servers = default_maven_server_urls(), - fetch_sources = False): + maven_servers = _settings_defaults["maven_servers"], + fetch_sources = _settings_defaults["fetch_sources"]): """Instantiates internal only repos for development and testing Args: @@ -67,3 +90,15 @@ def dev_deps_repositories( ], maven_servers = maven_servers, ) + +def _dev_deps_impl(module_ctx): + """Instantiate internal only repos for development and testing""" + tags = root_module_tags(module_ctx, _tag_classes.keys()) + settings = single_tag_values(module_ctx, tags.settings, _settings_defaults) + dev_deps_repositories(**settings) + +dev_deps = module_extension( + implementation = _dev_deps_impl, + tag_classes = _tag_classes, + doc = "Configures repositories used only for internal testing", +) diff --git a/scala/private/macros/bzlmod.bzl b/scala/private/macros/bzlmod.bzl new file mode 100644 index 000000000..39a0f5442 --- /dev/null +++ b/scala/private/macros/bzlmod.bzl @@ -0,0 +1,204 @@ +"""Utilities for working with Bazel modules + +These utilities facilitate the pattern of defining defaults, attrs, and tag +class dictionaries, as employed by: + +- `//scala/extensions:config.bzl` +- `//scala/extensions:deps.bzl` +- `//scala/private/extensions:dev_deps.bzl` +- `//scala/private:macros/test/bzlmod_test_ext.bzl` + +This pattern overcomes the restriction that tag class attrs are not iterable, +which would otherwise yield lots of initialization logic with duplicated default +values. + +These functions facilitate writing module extensions that need to implement +three common cases: + +- `root_module_tags`: for abiding the root module configuration only, returning + an empty struct if the root module doesn't specify any tags + +- `single_tag_values`: for enforcing that a tag appears at most once per module + as a regular and/or dev dependency, returning default values if unspecified + +- `repeated_tag_values`: for collecting unique tag instance values into a dict + of dicts, keyed by a particular tag `attr` + +For example: + +```py +_string_tag_defaults = { + "first": "foo", + "second": "bar", + "third": "baz", +} + +# A dict comprehension works if all attrs are of the same type. +_string_tag_attrs = { + k: attr.string(default = v) + for k, v in _string_tag_defaults.items() +} + +_mixed_tag_defaults = { + "fourth": "quux", + "fifth": ["xyzzy"], + "sixth": {"plugh": "frobozz"}, +} + +_mixed_tag_attrs = { + "fourth": attr.string(default = _mixed_tag_defaults["fourth"]), + "fifth": attr.string_list(default = _mixed_tag_defaults["fifth"]), + "sixth": attr.string_dict(default = _mixed_tag_defaults["sixth"]), +} + +_repeated_tag_attrs = { + "key": attr.string(mandatory = True), + "required_value": attr.string(mandatory = True), + "optional_value": attr.string(), +} + +_tag_classes = { + "string_tag": tag_class(attrs = _string_tag_attrs), + "mixed_tag": tag_class(attrs = _mixed_tag_attrs), + "repeated_tag": tag_class(attrs = _repeated_tag_attrs), +} + +def _example_ext_impl(module_ctx): + root_tags = root_module_tags(module_ctx, _tag_classes.keys()) + string_values_dict = single_tag_values( + module_ctx, + root_tags.string_tag, + _string_tag_defaults, + ) + mixed_values_dict = single_tag_values( + module_ctx, + root_tags.mixed_tag, + _mixed_tag_defaults, + ) + repeated_values_dict = repeated_tag_values( + root_tags.repeated_tag, + _repeated_tag_attrs, + ) + + some_macro_or_repo_rule_that_uses_these_tag_values( + name = "example_repo", + repeated = repeated_values_dict, + **(string_values_dict | mixed_values_dict), + ) + +example_ext = module_extension( + implementation = _example_ext_impl, + tag_classes = _tag_classes, +) +``` +""" + +def root_module_tags(module_ctx, tag_class_names): + """Returns the `bazel_module_tags` from the root `bazel_module`. + + If the root module doesn't use the module extension (`module_ctx` doesn't + contain the root module), returns a `struct` constructed from + `tag_class_names`. This is useful for configuring default values in that + case, without having to add extra module extension logic. + + Args: + module_ctx: the module extension context + tag_class_names: tag classes used to create a struct if no root module + detected + + Returns: + `bazel_module_tags` or a `struct` constructed from `tag_class_names` + """ + for module in module_ctx.modules: + if module.is_root: + return module.tags + return struct(**{name: [] for name in tag_class_names}) + +_single_tag_err = ( + "expected one regular tag instance and/or one dev_dependency instance, " + + "got %s:" +) + +def single_tag_values(module_ctx, tags, tag_defaults): + """Returns a dictionary of tag `attr` names to explicit or default values. + + Use for tags that should appear at most once in a module as a regular tag + and at most once as a `dev_dependency` tag. + + Nondefault values from a `dev_dependency` instance will override the regular + instance's values. + + Fails if `tags` contains more than two tag instances, if both are + `dev_dependency` or regular instances, or if the regular instance doesn't + come first. + + Args: + module_ctx: the module extension context + tags: a list of tag class values from a `bazel_module_tags` object + tag_defaults: a dictionary of tag attr names to default values + + Returns: + a dict of tag `attr` names to values + """ + if len(tags) == 0: + return tag_defaults + if len(tags) > 2: + fail(_single_tag_err % len(tags), *tags) + + result = {k: getattr(tags[0], k) for k in tag_defaults} + + if len(tags) == 2: + first_is_dev = module_ctx.is_dev_dependency(tags[0]) + second_is_dev = module_ctx.is_dev_dependency(tags[1]) + + if first_is_dev == second_is_dev: + tag_type = "dev_dependency" if first_is_dev else "regular" + fail(_single_tag_err % ("two %s instances" % (tag_type)), *tags) + + elif first_is_dev: + msg = "the dev_dependency instance before the regular instance" + fail(_single_tag_err % msg, *tags) + + dev_dep_values = {k: getattr(tags[1], k) for k in tag_defaults} + result.update({ + k: v + for k, v in dev_dep_values.items() + if v != tag_defaults[k] + }) + + return result + +def repeated_tag_values(tags, attr_dict): + """Compiles repeated tag instances into a dict of dicts. + + Returns a dict of dicts representing unique tag instance values, using the + first key from `attr_dict` as the key value. + + Fails if more than one tag instance has the same key value, regardless + of `dev_dependency` status. + + Args: + tags: a list of tag class values from a `bazel_module_tags` object + attr_dict: a dict from `attr` name to `attr` instance + + Returns: + a dict from tag instance key values to a dict representing all tag + instance values + """ + attr_names = attr_dict.keys() + key_name = attr_names[0] + instances = {} + result = {} + + for instance in tags: + values = {field: getattr(instance, field) for field in attr_names} + key = values.pop(key_name) + + if key in instances: + msg = "multiple tags with same %s:" % key_name + fail(msg, instances[key], instance) + + instances[key] = instance + result[key] = {k: v for k, v in values.items()} + + return result diff --git a/scala/private/macros/test/BUILD.bzlmod_test b/scala/private/macros/test/BUILD.bzlmod_test new file mode 100644 index 000000000..3a2611b8d --- /dev/null +++ b/scala/private/macros/test/BUILD.bzlmod_test @@ -0,0 +1,27 @@ +"""Used by test/shell/test_bzlmod_helpers.sh to test bzlmod.bzl.""" + +load( + "@test_tag_values//:results.bzl", + "FIRST", + "SECOND", + "THIRD", + "REPEATED", +) + +sh_binary( + name = "print-single-test-tag-values", + srcs = [":print-tag-values"], + args = ["%s %s %s" % (FIRST, SECOND, THIRD)], +) + +sh_binary( + name = "print-repeated-test-tag-values", + srcs = [":print-tag-values"], + args = ["'%s'" % str(REPEATED)], +) + +genrule( + name = "print-tag-values", + outs = ["print-tag-values.sh"], + cmd = "echo 'echo \"$$*\"' >$@", +) diff --git a/scala/private/macros/test/MODULE.bzlmod_test b/scala/private/macros/test/MODULE.bzlmod_test new file mode 100644 index 000000000..92ec10e2f --- /dev/null +++ b/scala/private/macros/test/MODULE.bzlmod_test @@ -0,0 +1,18 @@ +"""Used by test/shell/test_bzlmod_helpers.sh to test bzlmod.bzl.""" + +module(name = "test_module", version = "0.0.0") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "${rules_scala_dir}" +) + +test_ext = use_extension("//:bzlmod_test_ext.bzl", "test_ext") +use_repo(test_ext, "test_tag_values") + +dev_test_ext = use_extension( + "//:bzlmod_test_ext.bzl", + "test_ext", + dev_dependency = True, +) diff --git a/scala/private/macros/test/MODULE.bzlmod_test_root_module b/scala/private/macros/test/MODULE.bzlmod_test_root_module new file mode 100644 index 000000000..99c553886 --- /dev/null +++ b/scala/private/macros/test/MODULE.bzlmod_test_root_module @@ -0,0 +1,19 @@ +"""Used by test/shell/test_bzlmod_helpers.sh to test bzlmod.bzl. + +Used by `test_bzlmod_creates_fake_root_module_tags_when_unused_by_root_module` +to test `root_module_tags` when the extension isn't imported by the root module. +""" + +module(name = "test_root_module", version = "0.0.0") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "${rules_scala_dir}" +) + +bazel_dep(name = "test_module") +local_path_override( + module_name = "test_module", + path = "${test_module_dir}" +) diff --git a/scala/private/macros/test/bzlmod_test_ext.bzl b/scala/private/macros/test/bzlmod_test_ext.bzl new file mode 100644 index 000000000..b2864e9b2 --- /dev/null +++ b/scala/private/macros/test/bzlmod_test_ext.bzl @@ -0,0 +1,91 @@ +"""Used by test/shell/test_bzlmod_helpers.sh to test bzlmod.bzl. + +Defines a module extension with two tag classes: + +- `single_test_tag`: Contains three `attr.string` fields with nonempty default + values. Should have at most one regular instance and one `dev_dependency` + instance. Used to test `single_tag_values`. + +- `repeated_test_tag`: Contains a mandatory `key` attr, one `required` attr, and + one `optional` attr. Used to test `repeated_tag_values`. + +Generates `@test_tag_values//:results.bzl` from `_test_tag_results_bzl_template`. +`BUILD.bzlmod_test` imports the following symbols from this generated file: + +- For `single_test_tag`: the `FIRST`, `SECOND`, and `THIRD` string constants +- For `repeated_test_tag`: the `REPEATED` dict of dicts +""" + +load( + "@rules_scala//scala/private:macros/bzlmod.bzl", + "repeated_tag_values", + "root_module_tags", + "single_tag_values", +) + +visibility("private") + +_single_test_tag_defaults = { + "first": "foo", + "second": "bar", + "third": "baz", +} + +_single_test_tag_attrs = { + k: attr.string(default = v) + for k, v in _single_test_tag_defaults.items() +} + +_repeated_test_tag_attrs = { + "unique_key": attr.string(mandatory = True), + "required": attr.string(mandatory = True), + "optional": attr.string(), +} + +_tag_classes = { + "single_test_tag": tag_class(attrs = _single_test_tag_attrs), + "repeated_test_tag": tag_class(attrs = _repeated_test_tag_attrs), +} + +_test_tag_results_bzl_template = """ +FIRST = "{first}" +SECOND = "{second}" +THIRD = "{third}" +REPEATED = {repeated} +""" + +def _test_tag_results_repo_impl(rctx): + rctx.file("BUILD") + rctx.file( + "results.bzl", + _test_tag_results_bzl_template.format(**rctx.attr.test_tag_values), + ) + +_test_tag_results_repo = repository_rule( + implementation = _test_tag_results_repo_impl, + attrs = { + "test_tag_values": attr.string_dict(mandatory = True), + }, +) + +def _test_ext_impl(mctx): + root_tags = root_module_tags(mctx, _tag_classes.keys()) + single_values = single_tag_values( + mctx, + root_tags.single_test_tag, + _single_test_tag_defaults, + ) + repeated_values = repeated_tag_values( + root_tags.repeated_test_tag, + _repeated_test_tag_attrs, + ) + + _test_tag_results_repo( + name = "test_tag_values", + test_tag_values = single_values | {"repeated": str(repeated_values)}, + ) + +test_ext = module_extension( + implementation = _test_ext_impl, + tag_classes = _tag_classes, +) diff --git a/scala/toolchains_repo.bzl b/scala/toolchains_repo.bzl index 4d00ccf1d..ede982f7c 100644 --- a/scala/toolchains_repo.bzl +++ b/scala/toolchains_repo.bzl @@ -100,7 +100,7 @@ _scala_toolchains_repo = repository_rule( "scala_proto_options": attr.string_list( doc = ( "Protobuf generator options; " + - "scala_proto must also be True for this to take effect" + "`scala_proto` must also be `True` for this to take effect" ), ), "jmh": attr.bool( diff --git a/test/aspect/aspect.bzl b/test/aspect/aspect.bzl index 906c0f7fb..fbed7f4cd 100644 --- a/test/aspect/aspect.bzl +++ b/test/aspect/aspect.bzl @@ -5,6 +5,8 @@ sure the targets we expect are there. """ attr_aspects = ["_scala_toolchain", "deps"] +VisitedInfo = provider("Collection of visited targets", fields = ["visited"]) + def _stringify_label(label): s = str(label) if s.startswith("@@//"): @@ -15,12 +17,12 @@ def _stringify_label(label): def _aspect_impl(target, ctx): visited = [_stringify_label(target.label)] + for attr_name in attr_aspects: - if hasattr(ctx.rule.attr, attr_name): - for dep in getattr(ctx.rule.attr, attr_name): - if hasattr(dep, "visited"): - visited += dep.visited - return struct(visited = visited) + for dep in getattr(ctx.rule.attr, attr_name, []): + visited += dep[VisitedInfo].visited + + return VisitedInfo(visited = visited) test_aspect = aspect( attr_aspects = attr_aspects, @@ -56,7 +58,7 @@ def _aspect_testscript_impl(ctx): } content = "" for target in ctx.attr.targets: - visited = depset(sorted(target.visited)).to_list() + visited = depset(sorted(target[VisitedInfo].visited)).to_list() expected = depset(sorted(expected_deps[target.label.name])).to_list() if visited != expected: content += """ @@ -71,12 +73,12 @@ def _aspect_testscript_impl(ctx): visited = ", ".join(visited), ) - scriptFile = ctx.actions.declare_file("aspect_test.sh") + script_file = ctx.actions.declare_file("aspect_test.sh") ctx.actions.write( - output = scriptFile, + output = script_file, content = content, ) - return [DefaultInfo(files = depset([scriptFile]))] + return [DefaultInfo(files = depset([script_file]))] aspect_testscript = rule( implementation = _aspect_testscript_impl, diff --git a/test/proto_cross_repo_boundary/repo/MODULE.bazel b/test/proto_cross_repo_boundary/repo/MODULE.bazel new file mode 100644 index 000000000..20f4e4b26 --- /dev/null +++ b/test/proto_cross_repo_boundary/repo/MODULE.bazel @@ -0,0 +1,3 @@ +module(name = "proto_cross_repo_boundary") + +bazel_dep(name = "rules_proto", version = "7.1.0") diff --git a/test/shell/test_bzlmod_macros.sh b/test/shell/test_bzlmod_macros.sh new file mode 100755 index 000000000..1a1aa8a04 --- /dev/null +++ b/test/shell/test_bzlmod_macros.sh @@ -0,0 +1,244 @@ +#!/usr/bin/env bash +# +# Tests for //scala/private:macros/bzlmod.bzl + +set -e + +dir="$( cd "${BASH_SOURCE[0]%/*}" && echo "${PWD%/test/shell}" )" +test_source="${dir}/test/shell/${BASH_SOURCE[0]#*test/shell/}" +# shellcheck source=./test_runner.sh +. "${dir}"/test/shell/test_runner.sh +. "${dir}"/test/shell/test_helper.sh +runner=$(get_test_runner "${1:-local}") +export USE_BAZEL_VERSION=${USE_BAZEL_VERSION:-$(cat $dir/.bazelversion)} + +# Setup and teardown + +setup_suite() { + if [[ "$(bazel --version)" =~ ^bazel\ 6\. ]]; then + src_file="${test_source#$dir/}" + echo -e "${YELLOW}${src_file} not compatible with Bazel 6, skipping...${NC}" + test_source='/dev/null' + return + fi + + original_dir="$PWD" + test_tmpdir="${dir}/tmp/${BASH_SOURCE[0]##*/}" + test_tmpdir="${test_tmpdir%.*}" + test_srcs_dir="${dir}/scala/private/macros/test" + + mkdir -p "$test_tmpdir" + cd "$test_tmpdir" + + rules_scala_dir="../.." + test_tmpdir_base="${test_tmpdir##*/}" + test_module_bazel_regex="[^ ]+${test_tmpdir_base}/MODULE.bazel" + + local bk_bazel_rc="${dir}/tools/bazel.rc" + + if [[ -f "$bk_bazel_rc" ]]; then + # test_rules_scala_jdk21 from .bazelci/presubmit.yml needs this. + mkdir tools + cp "${bk_bazel_rc}" tools/ + fi +} + +teardown_suite() { + # Make sure bazel isn't still running for this workspace. + bazel clean --expunge_async 2>/dev/null + cd "$original_dir" + rm -rf "$test_tmpdir" +} + +setup_test_module() { + set -e + cp "${dir}"/.bazel{rc,version} "${test_srcs_dir}"/bzlmod_test_ext.bzl . + cp "${test_srcs_dir}/BUILD.bzlmod_test" 'BUILD' + + sed -e "s%\${rules_scala_dir}%${rules_scala_dir}%" \ + "${test_srcs_dir}/MODULE.bzlmod_test" > 'MODULE.bazel' + + printf '%s\n' "$@" >>'MODULE.bazel' +} + +# Test utilities + +bazel_run_args=('run' '--enable_bzlmod') +print_single_test_tag_values_target='//:print-single-test-tag-values' +print_repeated_test_tag_values_target='//:print-repeated-test-tag-values' + +print_single_test_tag_values() { + bazel "${bazel_run_args[@]}" "$print_single_test_tag_values_target" 2>&1 +} + +print_single_test_tag_values_should_fail_with_message() { + local expected=( + "expected one regular tag instance and/or one dev_dependency instance," + "${1}: 'single_test_tag' tag at ${test_module_bazel_regex}:" + ) + + action_should_fail_with_message "${expected[*]}" \ + "${bazel_run_args[@]}" "$print_single_test_tag_values_target" +} + +print_repeated_test_tag_values() { + bazel "${bazel_run_args[@]}" "$print_repeated_test_tag_values_target" 2>&1 +} + +# Test cases + +test_bzlmod_single_tag_values_returns_defaults_when_no_root_tag() { + setup_test_module + + assert_matches 'foo bar baz$' "$(print_single_test_tag_values)" +} + +test_bzlmod_creates_fake_root_module_tags_when_unused_by_root_module() { + # This setup is a bit more involved because this is the only test that sets + # up the test module as a non-root module. + local test_module_dir="../${test_tmpdir_base}_test_module" + + mkdir "$test_module_dir" + cd "$test_module_dir" + setup_test_module + cd "$test_tmpdir" + + sed -e "s%\${rules_scala_dir}%${rules_scala_dir}%" \ + -e "s%\${test_module_dir}%${test_module_dir}%" \ + "${test_srcs_dir}/MODULE.bzlmod_test_root_module" > 'MODULE.bazel' + + local target='@test_module//:print-single-test-tag-values' + local tag_values="$(bazel run --enable_bzlmod "$target")" + + assert_matches 'foo bar baz$' "$tag_values" + rm -rf "$test_module_dir" +} + +test_bzlmod_single_tag_values_returns_regular_root_tag_values() { + setup_test_module \ + 'test_ext.single_test_tag(first = "quux", third = "plugh")' + + assert_matches 'quux bar plugh$' "$(print_single_test_tag_values)" +} + +test_bzlmod_single_tag_values_returns_dev_root_tag_values() { + setup_test_module \ + 'dev_test_ext.single_test_tag(first = "quux", third = "plugh")' + + assert_matches 'quux bar plugh$' "$(print_single_test_tag_values)" +} + +test_bzlmod_single_tag_values_combines_regular_and_dev_dep_tags() { + setup_test_module \ + 'test_ext.single_test_tag(first = "quux", third = "plugh")' \ + 'dev_test_ext.single_test_tag(second = "xyzzy", third = "frobozz")' + + # Dev values matching the default won't overwrite regular tag values. + assert_matches 'quux xyzzy frobozz$' "$(print_single_test_tag_values)" +} + +test_bzlmod_single_tag_values_fails_if_more_than_two_tags() { + setup_test_module \ + 'test_ext.single_test_tag()' \ + 'dev_test_ext.single_test_tag()' \ + 'dev_test_ext.single_test_tag(second = "not", third = "happening")' + + print_single_test_tag_values_should_fail_with_message "got 3" +} + +test_bzlmod_single_tag_values_fails_if_dev_tag_before_regular() { + setup_test_module \ + 'dev_test_ext.single_test_tag()' \ + 'test_ext.single_test_tag(first = "should be, but isn''t")' + + print_single_test_tag_values_should_fail_with_message \ + "got the dev_dependency instance before the regular instance" +} + +test_bzlmod_single_tag_values_fails_if_two_regular_tags() { + setup_test_module \ + 'test_ext.single_test_tag(first = "of two")' \ + 'test_ext.single_test_tag(second = "of two")' + + print_single_test_tag_values_should_fail_with_message \ + "got two regular instances" +} + +test_bzlmod_single_tag_values_fails_if_two_dev_tags() { + setup_test_module \ + 'dev_test_ext.single_test_tag(first = "of two")' \ + 'dev_test_ext.single_test_tag(second = "of two")' + + print_single_test_tag_values_should_fail_with_message \ + "got two dev_dependency instances" +} + +test_bzlmod_repeated_tag_values_for_zero_instances() { + setup_test_module + + assert_matches '\{\}$' "$(print_repeated_test_tag_values)" +} + +test_bzlmod_repeated_tag_values_for_one_instance() { + setup_test_module \ + 'test_ext.repeated_test_tag(unique_key = "foo", required = "bar")' + + assert_matches '\{"foo": \{"required": "bar", "optional": ""\}\}$' \ + "$(print_repeated_test_tag_values)" +} + +test_bzlmod_repeated_tag_values_for_multiple_instances() { + setup_test_module \ + 'test_ext.repeated_test_tag(unique_key = "foo", required = "bar")' \ + 'test_ext.repeated_test_tag(' \ + ' unique_key = "baz",' \ + ' required = "quux",' \ + ' optional = "xyzzy",' \ + ')' \ + 'dev_test_ext.repeated_test_tag(' \ + ' unique_key = "plugh",' \ + ' required = "frobozz",' \ + ')' + + local expected=( + '\{"foo": \{"required": "bar", "optional": ""\},' + '"baz": \{"required": "quux", "optional": "xyzzy"\},' + '"plugh": \{"required": "frobozz", "optional": ""\}\}$' + ) + + assert_matches "${expected[*]}" "$(print_repeated_test_tag_values)" +} + +test_bzlmod_repeated_tag_values_fails_on_duplicate_key() { + setup_test_module \ + 'test_ext.repeated_test_tag(unique_key = "foo", required = "bar")' \ + 'dev_test_ext.repeated_test_tag(unique_key = "foo", required = "baz")' + + local expected=( + "multiple tags with same unique_key:" + "'repeated_test_tag' tag at ${test_module_bazel_regex}:" + ) + + action_should_fail_with_message "${expected[*]}" \ + "${bazel_run_args[@]}" "$print_repeated_test_tag_values_target" +} + +# Run tests +# To skip a test, add a `_` prefix to its function name. +# To run a specific test, set the `RULES_SCALA_TEST_ONLY` env var to its name. + +setup_suite + +while IFS= read -r line; do + if [[ "$line" =~ ^_?(test_[A-Za-z0-9_]+)\(\)\ ?\{$ ]]; then + test_name="${BASH_REMATCH[1]}" + + if [[ "${line:0:1}" == '_' ]]; then + echo -e "${YELLOW}skipping ${test_name}${NC}" + else + "$runner" "$test_name" + fi + fi +done <"$test_source" + +teardown_suite diff --git a/test/shell/test_bzlmod_tidy.sh b/test/shell/test_bzlmod_tidy.sh new file mode 100755 index 000000000..238642428 --- /dev/null +++ b/test/shell/test_bzlmod_tidy.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +set -e + +dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +rootdir="$(cd "${dir}/../.." && pwd)" +. "${dir}"/test_runner.sh +runner=$(get_test_runner "${1:-local}") +test_tmpdir="${rootdir}/tmp/lint" +mkdir -p "$test_tmpdir" + +check_module_bazel() { + local repo_path="${1%MODULE.bazel}" + local mod_orig="${test_tmpdir}/MODULE.lint" + local mod_diff="${test_tmpdir}/MODULE.diff" + + echo -e "${GREEN}INFO:${NC} linting $1" + repo_path="${repo_path:-.}" + + cd "${rootdir}/${repo_path}" + cp MODULE.bazel "$mod_orig" + + bazel mod tidy + bazel shutdown + + if ! diff -u "$mod_orig" MODULE.bazel >"$mod_diff"; then + echo -e "${RED}ERROR:${NC}" \ + "\`bazel mod tidy\` produced changes in ${repo_path%.}MODULE.bazel:" + echo "$(< "$mod_diff")" + exit 1 + fi +} + +while IFS= read -r module_file; do + $runner check_module_bazel "$module_file" +done < <(git ls-files '**MODULE.bazel') + +rm -rf "${test_tmpdir}" diff --git a/test/shell/test_helper.sh b/test/shell/test_helper.sh index 11262d33c..ad82ce8af 100755 --- a/test/shell/test_helper.sh +++ b/test/shell/test_helper.sh @@ -142,3 +142,21 @@ jar_contains_files() { fi done } + +_print_error_msg() { + printf '%b' "$RED" + printf '%s\n' "$@" + printf '%b' "$NC" +} + +assert_matches() { + local expected="$1" + local actual="$2" + + if [[ ! "$actual" =~ $expected ]]; then + _print_error_msg "Value did not match regular expression" \ + "Expected: \"$expected\"" \ + "Actual: \"$actual\"" + return 1 + fi +} diff --git a/test/shell/test_runner.sh b/test/shell/test_runner.sh index db3a7aa68..8f65db104 100644 --- a/test/shell/test_runner.sh +++ b/test/shell/test_runner.sh @@ -5,6 +5,7 @@ NC='\033[0m' GREEN='\033[0;32m' RED='\033[0;31m' +YELLOW='\033[0;33m' run_test_ci() { # spawns the test to new process diff --git a/test_cleanup.sh b/test_cleanup.sh new file mode 100755 index 000000000..4cd9aaa63 --- /dev/null +++ b/test_cleanup.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# +# Cleans the output base and shuts down the Bazel servers of nested repos. +# +# There shouldn't be a need to run this regularly. However, if disk space gets +# tight, this will clean all nested repos. + +dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) + +while IFS= read -r repo; do + repo="${repo%/MODULE.bazel}" + echo "cleaning: $repo" + + cd "$repo" + bazel clean --expunge_async 2>/dev/null + cd "$dir" +done < <(git ls-files '*/*MODULE.bazel') diff --git a/test_cross_build/BUILD b/test_cross_build/BUILD new file mode 100644 index 000000000..e69de29bb diff --git a/test_cross_build/MODULE.bazel b/test_cross_build/MODULE.bazel new file mode 100644 index 000000000..978207734 --- /dev/null +++ b/test_cross_build/MODULE.bazel @@ -0,0 +1,64 @@ +"""Bazel module ./test/shell/test_examples.sh tests""" + +module(name = "cross_build") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + scala_version = "3.1.3", + scala_versions = [ + "2.11.12", + "2.12.20", + "2.13.16", + "3.1.3", + "3.2.2", + "3.3.5", + ], +) +use_repo(scala_config, "rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() +scala_deps.scalafmt() +scala_deps.scalatest() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/test_cross_build/protobuf.patch b/test_cross_build/protobuf.patch new file mode 120000 index 000000000..93dccac64 --- /dev/null +++ b/test_cross_build/protobuf.patch @@ -0,0 +1 @@ +../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/test_lint.sh b/test_lint.sh index 026aa833a..14b95e3be 100755 --- a/test_lint.sh +++ b/test_lint.sh @@ -2,4 +2,11 @@ set -eou pipefail +dir="${BASH_SOURCE[0]%/*}" +dir="$( cd "${dir:-.}" && pwd )" + bazel run //tools:lint_check + +RULES_SCALA_TEST_ONLY="${RULES_SCALA_TEST_ONLY:-}" +RULES_SCALA_TEST_VERBOSE="${RULES_SCALA_TEST_VERBOSE:-}" +. "${dir}/test/shell/test_bzlmod_tidy.sh" diff --git a/test_rules_scala.sh b/test_rules_scala.sh index 554da4735..d05abddaa 100755 --- a/test_rules_scala.sh +++ b/test_rules_scala.sh @@ -12,6 +12,7 @@ test_dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/test/shell . "${test_dir}"/test_runner.sh runner=$(get_test_runner "${1:-local}") +. "${test_dir}"/test_bzlmod_macros.sh $runner bazel build test/... #$runner bazel build "test/... --all_incompatible_changes" $runner bazel test test/... diff --git a/test_version.sh b/test_version.sh index 3c813fe00..a38148510 100755 --- a/test_version.sh +++ b/test_version.sh @@ -41,6 +41,14 @@ compilation_should_fail() { fi } +teardown_test_repo() { + local test_dir="$1" + + #make sure bazel still not running or consuming space for this workspace + bazel clean --expunge_async 2>/dev/null + rm -rf "$test_dir" +} + run_in_test_repo() { local SCALA_VERSION=${SCALA_VERSION:-$SCALA_VERSION_DEFAULT} @@ -57,28 +65,41 @@ run_in_test_repo() { cp -r $test_target $NEW_TEST_DIR local scrooge_ws="" + local scrooge_mod="" if [[ -n "$TWITTER_SCROOGE_VERSION" ]]; then local version_param="version = \"$TWITTER_SCROOGE_VERSION\"" - scrooge_ws="$version_param\\n" + scrooge_ws="$version_param" + scrooge_mod="scrooge_repos.settings($version_param)\\n" fi sed -e "s%\${twitter_scrooge_repositories}%${scrooge_ws}%" \ WORKSPACE.template >> $NEW_TEST_DIR/WORKSPACE + sed -e "s%\${twitter_scrooge_repositories}%${scrooge_mod}%" \ + MODULE.bazel.template >> $NEW_TEST_DIR/MODULE.bazel cp ../.bazel{rc,version} scrooge_repositories.bzl $NEW_TEST_DIR/ + cp ../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ + $NEW_TEST_DIR/protobuf.patch cd $NEW_TEST_DIR - ${test_command} - RESPONSE_CODE=$? + #make sure bazel still not running or consuming space for this workspace + trap "teardown_test_repo '$PWD'" EXIT - #make sure bazel still not running for this workspace - bazel shutdown + ${test_command} + exit $? +} - cd .. - rm -rf $NEW_TEST_DIR +check_module_bazel_template() { + cp MODULE.bazel MODULE.orig \ + && bazel mod --enable_bzlmod tidy \ + && diff -u MODULE.orig MODULE.bazel +} - exit $RESPONSE_CODE +test_check_module_bazel_template() { + run_in_test_repo "check_module_bazel_template" \ + "bzlmod_tidy" \ + "version_specific_tests_dir/" } test_scala_version() { @@ -131,6 +152,7 @@ dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) runner=$(get_test_runner "${1:-local}") export USE_BAZEL_VERSION=${USE_BAZEL_VERSION:-$(cat $dir/.bazelversion)} +TEST_TIMEOUT=15 $runner test_check_module_bazel_template TEST_TIMEOUT=15 $runner test_scala_version "${scala_2_12_version}" TEST_TIMEOUT=15 $runner test_scala_version "${scala_2_13_version}" diff --git a/test_version/MODULE.bazel.template b/test_version/MODULE.bazel.template new file mode 100644 index 000000000..621165fdd --- /dev/null +++ b/test_version/MODULE.bazel.template @@ -0,0 +1,77 @@ +"""Bazel module template for //:test_version.sh tests""" + +module(name = "rules_scala_test_version") + +bazel_dep(name = "rules_java", version = "8.11.0") +bazel_dep(name = "rules_proto", version = "7.1.0") +bazel_dep(name = "rules_cc", version = "0.1.1") +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../..", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + enable_compiler_dependency_tracking = True, +) +use_repo(scala_config, "rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.scala() +scala_deps.scala_proto( + options = ["grpc"], +) +scala_deps.scalatest() +scala_deps.specs2() + +scrooge_repos = use_extension( + "//:scrooge_repositories.bzl", + "scrooge_repositories_ext", +) +${twitter_scrooge_repositories}use_repo( + scrooge_repos, + "io_bazel_rules_scala_scrooge_core", + "io_bazel_rules_scala_scrooge_generator", + "io_bazel_rules_scala_util_core", + "io_bazel_rules_scala_util_logging", + "twitter_scrooge_test_toolchain", +) + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + "@rules_scala//scala:unused_dependency_checker_error_toolchain", + "@twitter_scrooge_test_toolchain//...:all", +) diff --git a/test_version/protobuf.patch b/test_version/protobuf.patch new file mode 120000 index 000000000..93dccac64 --- /dev/null +++ b/test_version/protobuf.patch @@ -0,0 +1 @@ +../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/test_version/scrooge_repositories.bzl b/test_version/scrooge_repositories.bzl index b6a1ba08b..9f7ba5440 100644 --- a/test_version/scrooge_repositories.bzl +++ b/test_version/scrooge_repositories.bzl @@ -112,3 +112,20 @@ def scrooge_repositories(version = None): twitter_scrooge = True, twitter_scrooge_deps = toolchain_deps, ) + +_settings = tag_class( + attrs = { + "version": attr.string(mandatory = True), + }, +) + +def _scrooge_repositories_ext_impl(module_ctx): + settings = module_ctx.modules[0].tags.settings + scrooge_repositories(settings[0].version if len(settings) != 0 else None) + +scrooge_repositories_ext = module_extension( + implementation = _scrooge_repositories_ext_impl, + tag_classes = { + "settings": _settings, + }, +) diff --git a/third_party/test/example_external_workspace/BUILD b/third_party/test/example_external_workspace/BUILD new file mode 100644 index 000000000..e69de29bb diff --git a/third_party/test/example_external_workspace/MODULE.bazel b/third_party/test/example_external_workspace/MODULE.bazel new file mode 100644 index 000000000..1da9cab63 --- /dev/null +++ b/third_party/test/example_external_workspace/MODULE.bazel @@ -0,0 +1,43 @@ +"""Bazel example module for several top level tests""" + +module(name = "example_external_workspace") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../../..", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.scala() +scala_deps.scalatest() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/third_party/test/example_external_workspace/protobuf.patch b/third_party/test/example_external_workspace/protobuf.patch new file mode 120000 index 000000000..0c9d2569d --- /dev/null +++ b/third_party/test/example_external_workspace/protobuf.patch @@ -0,0 +1 @@ +../../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/third_party/test/new_local_repo/MODULE.bazel b/third_party/test/new_local_repo/MODULE.bazel new file mode 100644 index 000000000..796331ee2 --- /dev/null +++ b/third_party/test/new_local_repo/MODULE.bazel @@ -0,0 +1 @@ +module(name = "test_new_local_repo") diff --git a/third_party/test/proto/MODULE.bazel b/third_party/test/proto/MODULE.bazel new file mode 100644 index 000000000..e9f07c6e5 --- /dev/null +++ b/third_party/test/proto/MODULE.bazel @@ -0,0 +1,44 @@ +"""Bazel module ./test/shell/test_scala_proto_library.sh tests""" + +module(name = "proto") + +bazel_dep(name = "rules_scala") +local_path_override( + module_name = "rules_scala", + path = "../../..", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +scala_deps.scala() +scala_deps.scala_proto() + +scala_protoc = use_extension( + "@rules_scala//scala/extensions:protoc.bzl", + "scala_protoc", + dev_dependency = True, +) +use_repo(scala_protoc, "rules_scala_protoc_toolchains") + +register_toolchains( + "@rules_scala_protoc_toolchains//...:all", + "@rules_scala//scala:unused_dependency_checker_error_toolchain", + dev_dependency = True, +) + +# Temporarily required for `protoc` toolchainization until resolution of +# protocolbuffers/protobuf#19679. +bazel_dep( + name = "protobuf", + version = "30.1", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + patch_strip = 1, + patches = ["//:protobuf.patch"], + version = "30.1", +) diff --git a/third_party/test/proto/protobuf.patch b/third_party/test/proto/protobuf.patch new file mode 120000 index 000000000..0c9d2569d --- /dev/null +++ b/third_party/test/proto/protobuf.patch @@ -0,0 +1 @@ +../../../protoc/0001-protobuf-19679-rm-protoc-dep.patch \ No newline at end of file diff --git a/tools/bazel.rc.buildkite b/tools/bazel.rc.buildkite index 479197f37..2a98b5d02 100644 --- a/tools/bazel.rc.buildkite +++ b/tools/bazel.rc.buildkite @@ -1,6 +1,5 @@ -# Switch to --noenable_workspace when Bzlmod lands. -# https://github.com/bazelbuild/rules_scala/issues/1482 -common --enable_workspace --noenable_bzlmod +# Remove once Bazel 8 becomes the default supported version. +common --noenable_workspace --incompatible_use_plus_in_repo_names # Remove once proto toolchainization becomes the default # - https://bazel.build/reference/command-line-reference#flag--incompatible_enable_proto_toolchain_resolution