diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 000000000..76a97d775 --- /dev/null +++ b/.bazelignore @@ -0,0 +1,6 @@ +# 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 +third_party/test/example_external_workspace +third_party/test/proto diff --git a/.bazelrc b/.bazelrc index 498871943..b366ea644 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,9 +1,8 @@ +# Remove once Bazel 7.5.0 becomes the default supported version. +common --enable_bzlmod + build --enable_platform_specific_config #Windows needs --worker_quit_after_build due to workers not being shut down when the compiler tools need to be rebuilt (resulting in 'file in use' errors). See Bazel Issue#10498. build:windows --worker_quit_after_build --enable_runfiles - -# Remove upon completing Bzlmod compatibility work. -# - https://github.com/bazelbuild/rules_scala/issues/1482 -build --noenable_bzlmod diff --git a/.bcr/config.yml b/.bcr/config.yml new file mode 100644 index 000000000..e69de29bb 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..0ef780fc0 --- /dev/null +++ b/.bcr/presubmit.yml @@ -0,0 +1,14 @@ +# We recommend included a bcr test workspace that exercises your ruleset with bzlmod. +# For an example, see https://github.com/aspect-build/bazel-lib/tree/main/e2e/bzlmod. +bcr_test_module: + module_path: "examples/crossbuild" + matrix: + platform: ["debian10", "macos", "ubuntu2004", "windows"] + bazel: [6.x, 7.x] + tasks: + run_tests: + name: "Run test module" + platform: ${{ platform }} + bazel: ${{ bazel }} + 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/.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..129244659 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,290 @@ +"""Bazel module definition for rules_scala""" + +module( + name = "rules_scala", + version = "7.0.0", + compatibility_level = 7, + bazel_compatibility = [">=6.5.0", "<8.0.0"], +) + +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.15", +] + +SCALA_3_VERSIONS = [ + "3.1.3", + "3.3.5", + "3.5.2", + "3.6.3", +] + +SCALA_VERSIONS = SCALA_2_VERSIONS + SCALA_3_VERSIONS + +bazel_dep(name = "bazel_skylib", version = "1.7.1") + +# Bazel 6 breaks with any higher version of `rules_cc`, because: +# - 0.0.10 requires Bazel 7 to define `CcSharedLibraryHintInfo` +# - 0.0.13 and up don't support `protobuf` v21.7, requiring at least v27.0 +# - 0.1.0 should work, but requires `stardoc` 0.7.0, which requires Bazel 7 +# (though it's a `dev_dependency`, it still gets pulled in during module +# resolution, breaking the build) +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +bazel_dep(name = "rules_java", version = "7.12.4") +bazel_dep(name = "rules_proto", version = "6.0.2") + +# For now, users are revlocked to protobuf-21.7 or protobuf-25.5 (which doesn't +# build under Bazel 6). +bazel_dep( + name = "protobuf", + version = "21.7", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + version = "21.7", +) + +scala_config = use_extension( + "//scala/extensions:config.bzl", + "scala_config", +) +use_repo(scala_config, "io_bazel_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") + +# Register some of our testing toolchains first when building our repo. +register_toolchains( + "//scala:unused_dependency_checker_error_toolchain", + "//test/proto:scalapb_toolchain", + "//test/toolchains:java21_toolchain_definition", + dev_dependency = True, +) + +use_repo( + scala_deps, + "rules_scala_toolchains", + "scala_compiler_sources", +) + +register_toolchains("@rules_scala_toolchains//...:all") + +# Dev dependencies + +dev_deps = use_extension( + "//scala/extensions:deps.bzl", + "scala_deps", + dev_dependency = True, +) +dev_deps.toolchains( + jmh = True, + scala_proto = True, + #scala_proto_enable_all_options = True, + scalafmt = True, + testing = True, + #scalatest = True, + #junit = True, + #specs2 = True, + twitter_scrooge = True, +) + +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. + ["scala_proto_rules_scalapb_protoc_gen"] + if not scala_version.startswith("2.11.") else [] + ) + ] + 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 = "platforms", + version = "0.0.11", + dev_dependency = True, +) +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.0") + +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.29.0 +go_deps.module( + path = "golang.org/x/tools", + sum = "h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=", + version = "v0.30.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 = "0.38.0", dev_dependency = True) diff --git a/README.md b/README.md index b1aed2e88..cfff267a8 100644 --- a/README.md +++ b/README.md @@ -39,16 +39,122 @@ 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 `WORKSPACE` file and update -versions with their sha256s if needed. This snippet is designed to ensure that -users pick up the 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()`. - As of version 7.0.0, __`rules_scala` no longer requires the `io_bazel_rules_scala` repository name__ unless your `BUILD` files or those of your dependencies require it (bazelbuild/rules_scala#1696). +```py +# MODULE.bazel + +# You can add `repo_name = "io_bazel_rules_scala"` if you still need it. +bazel_dep(name = "rules_scala", version = "7.0.0") + +# 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 external loader like +# https://github.com/bazelbuild/rules_jvm_external. +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) + +scala_config.settings(scala_version = "2.13.15") + +# 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. On production +# projects, you may consider defining a custom toolchain to use your project's +# required dependencies instead. +# +# Optional builtin rules_scala toolchains may be configured by setting the +# appropriate parameter to `True`. See the documentation of `_toolchains_attrs` +# from `scala/extensions/deps.bzl` for details. +scala_deps.toolchains( + scalatest = True, +) +``` + +### Resolving `protobuf` conflicts + +For rules_scala 7.x, `scala_proto` and `scalafmt` users are revlocked between +protobuf-v21.7 and protobuf-v25.5 ([which requires compiler flags to build under +Bazel 6](#6.5.0)). rules_scala 8.0.0 will drop Bazel 6.5.0 Bzlmod support and +support protobuf-v28.2 and later. See [Compatible Bazel +versions](#compatible-bazel-versions) below for details regarding these +`protobuf` related restrictions. + +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 Bazel 6.5.0 support + +One primary objective of `rules_scala` 7.x is to enable existing users to +migrate to Bazel 7. [__`rules_scala` 8.0.0 will drop support for Bazel 6.5.0 +Bzlmod builds__](#6.5.0). + +If you're still on Bazel 6.5.0 for now, you will need to add the following +stanza to your `MODULE.bazel` file: + +```py +# Bazel 6 breaks with any higher version of `rules_cc`, because: +# +# - 0.0.10 requires Bazel 7 to define `CcSharedLibraryHintInfo` +# +# - 0.0.13 and up don't support `protobuf` v21.7, requiring at least v27.0 +# +# - 0.1.0 should work, but requires `stardoc` 0.7.0, which requires Bazel 7 +# (though it's a `dev_dependency`, it still gets pulled in during module +# resolution, breaking the build) +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) +``` + +### Legacy `WORKSPACE` configuration + +Another primary objective of `rules_scala` 7.x is to enable existing users to +migrate to Bzlmod. `WORKSPACE` will continue to work in `rules_scala` 8.0.0, for +Bazel 6.5.0, 7.5.0, and 8, but [__`WORKSPACE` is going away in Bazel +9__][bazel-9]. + +[bazel-9]: https://bazel.build/external/migration + +If you continue to use `WORKSPACE`, add the following stanza to your `WORKSPACE` +file and update versions with their sha256s if needed. This stanza is designed +to ensure that users pick up the 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()`. + Note that __`rules_scala` 7 introduces a new `scala_toolchains()` API that is very different from `rules_scala` 6__. For details on what's changed, see the [New `scala_toolchains()` API for `WORKSPACE`](#new-toolchains-api) section @@ -247,7 +353,7 @@ maximum available at the time of writing. | Bazel/Dependency | `rules_scala` 7.x | `rules_scala` 8.x
(Coming soon! See bazelbuild/rules_scala#1482 and bazelbuild/rules_scala#1652.) | | :-: | :-: | :-: | -| Bazel versions using Bzlmod
(Coming soon! See bazelbuild/rules_scala#1482.) | 6.5.0, 7.5.0 | 7.5.0, 8.x | +| Bazel versions using Bzlmod | 6.5.0, 7.5.0 | 7.5.0, 8.x | | Bazel versions using `WORKSPACE` | 6.5.0, 7.5.0 | 6.5.0, 7.5.0, 8.x
(see the [notes on 6.5.0 compatibility](#6.5.0)) | | `protobuf` | v21.7
(can support up to v25.5) | v29.3 | | `abseil-cpp` | 20220623.1 | 20250127.0 | @@ -478,18 +584,17 @@ same exact call will also work in `MODULE.bazel`. #### 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 `scala_deps`. -### 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 stanza for the `scala_toolchains()` stanza above would be: ```py bazel_dep(name = "rules_scala", version = "7.0.0") @@ -512,7 +617,7 @@ scala_deps.toolchains( ) ``` -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 @@ -521,8 +626,7 @@ 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. ### Embedded resource paths no longer begin with `external/` diff --git a/WORKSPACE.bzlmod b/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/dt_patches/compiler_sources/MODULE.bazel b/dt_patches/compiler_sources/MODULE.bazel new file mode 100644 index 000000000..c730e803b --- /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, "io_bazel_rules_scala_config") diff --git a/dt_patches/compiler_sources/WORKSPACE.bzlmod b/dt_patches/compiler_sources/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/dt_patches/compiler_sources/extensions.bzl b/dt_patches/compiler_sources/extensions.bzl index 0cf38b169..7bd7ade0d 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..40b98a4d3 --- /dev/null +++ b/dt_patches/test_dt_patches/MODULE.bazel @@ -0,0 +1,61 @@ +"""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 = "../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + enable_compiler_dependency_tracking = True, +) +use_repo(scala_config, "io_bazel_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, +) + +register_toolchains("//:dt_scala_toolchain") diff --git a/dt_patches/test_dt_patches/WORKSPACE.bzlmod b/dt_patches/test_dt_patches/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb 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..b8a21d8e8 --- /dev/null +++ b/dt_patches/test_dt_patches_user_srcjar/MODULE.bazel @@ -0,0 +1,173 @@ +"""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 = "../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + enable_compiler_dependency_tracking = True, +) +use_repo(scala_config, "io_bazel_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, +) + +# 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/scala3-compiler_3/3.1.3/scala3-compiler_3-3.1.3-sources.jar", + integrity = "sha384-4J2ihR1QSdP5cvL3y2OUfw4uUX/hsQqcPlJV+IrQdsM/soiIAYfoEeIEt6vl3xBk", + version = "3.1.3", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.2.2/scala3-compiler_3-3.2.2-sources.jar", + sha256 = "669d580fc4a8d3c2e2d13d5735ae9be05d567613fe44482de5bcc5e2e2ee89ea", + 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.2/scala3-compiler_3-3.6.2-sources.jar", + version = "3.6.2", +) +scala_deps.compiler_srcjar( + url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.6.3/scala3-compiler_3-3.6.3-sources.jar", + version = "3.6.3", +) + +register_toolchains("//:dt_scala_toolchain") diff --git a/dt_patches/test_dt_patches_user_srcjar/WORKSPACE.bzlmod b/dt_patches/test_dt_patches_user_srcjar/WORKSPACE.bzlmod 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..a3c2ba7a8 --- /dev/null +++ b/examples/crossbuild/MODULE.bazel @@ -0,0 +1,40 @@ +"""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 = "../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +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.15", + "3.3.5", + ], +) +use_repo(scala_config, "io_bazel_rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.toolchains( + scalatest = True, +) diff --git a/examples/crossbuild/WORKSPACE.bzlmod b/examples/crossbuild/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/examples/overridden_artifacts/.bazelrc b/examples/overridden_artifacts/.bazelrc new file mode 100644 index 000000000..005efba2f --- /dev/null +++ b/examples/overridden_artifacts/.bazelrc @@ -0,0 +1 @@ +import ../../.bazelrc diff --git a/examples/overridden_artifacts/.bazelversion b/examples/overridden_artifacts/.bazelversion new file mode 100644 index 000000000..f22d756da --- /dev/null +++ b/examples/overridden_artifacts/.bazelversion @@ -0,0 +1 @@ +6.5.0 diff --git a/examples/overridden_artifacts/BUILD b/examples/overridden_artifacts/BUILD new file mode 100644 index 000000000..10bac303d --- /dev/null +++ b/examples/overridden_artifacts/BUILD @@ -0,0 +1,12 @@ +load("@rules_scala//scala:scala.bzl", "scala_library", "scala_test") + +scala_library( + name = "hello", + srcs = ["Hello.scala"], +) + +scala_test( + name = "hello-test", + srcs = ["HelloTest.scala"], + deps = [":hello"], +) diff --git a/examples/overridden_artifacts/Hello.scala b/examples/overridden_artifacts/Hello.scala new file mode 100644 index 000000000..d65a1270a --- /dev/null +++ b/examples/overridden_artifacts/Hello.scala @@ -0,0 +1,4 @@ +package overriddenartifactstest + +class Hello(version: String): + def greetings(): String = "Hello, World! This is Scala " + version + "." diff --git a/examples/overridden_artifacts/HelloTest.scala b/examples/overridden_artifacts/HelloTest.scala new file mode 100644 index 000000000..262b1c28d --- /dev/null +++ b/examples/overridden_artifacts/HelloTest.scala @@ -0,0 +1,12 @@ +package overriddenartifactstest + +import org.scalatest.funsuite.AnyFunSuite + +class HelloTest extends AnyFunSuite: + test("greetings includes the correct Scala version number") { + val hello = new Hello(util.Properties.versionNumberString) + + // Apparently Scala 3 code will still return a Scala 2 version number: + // - https://users.scala-lang.org/t/what-scala-library-version-is-used-by-which-scala-3-version/9999 + assert(hello.greetings().endsWith("2.13.14.")) + } diff --git a/examples/overridden_artifacts/MODULE.bazel b/examples/overridden_artifacts/MODULE.bazel new file mode 100644 index 000000000..c0f934e62 --- /dev/null +++ b/examples/overridden_artifacts/MODULE.bazel @@ -0,0 +1,67 @@ +"""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 = "../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +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", +) + +scala_deps.settings( + fetch_sources = False, +) + +# 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.toolchains( + scalatest = True, +) diff --git a/examples/overridden_artifacts/WORKSPACE b/examples/overridden_artifacts/WORKSPACE new file mode 100644 index 000000000..05aa04810 --- /dev/null +++ b/examples/overridden_artifacts/WORKSPACE @@ -0,0 +1,94 @@ +workspace(name = "overridden_artifacts") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +local_repository( + name = "rules_scala", + path = "../..", +) + +load("@rules_scala//scala:deps.bzl", "rules_scala_dependencies") + +rules_scala_dependencies() + +load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains") + +rules_java_dependencies() + +load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") + +bazel_skylib_workspace() + +http_archive( + name = "rules_python", + sha256 = "ca2671529884e3ecb5b79d6a5608c7373a82078c3553b1fa53206e6b9dddab34", + strip_prefix = "rules_python-0.38.0", + url = "https://github.com/bazelbuild/rules_python/releases/download/0.38.0/rules_python-0.38.0.tar.gz", +) + +load("@rules_python//python:repositories.bzl", "py_repositories") + +py_repositories() + +load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") + +protobuf_deps() + +rules_java_toolchains() + +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") + +rules_proto_dependencies() + +load("@rules_proto//proto:setup.bzl", "rules_proto_setup") + +rules_proto_setup() + +load("@rules_proto//proto:toolchains.bzl", "rules_proto_toolchains") + +rules_proto_toolchains() + +load("@rules_scala//:scala_config.bzl", "scala_config") + +scala_config(scala_version = "3.3.5") + +load( + "@rules_scala//scala:toolchains.bzl", + "scala_register_toolchains", + "scala_toolchains", +) + +scala_toolchains( + # 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`. + overridden_artifacts = { + "io_bazel_rules_scala_scala_library": { + "artifact": "org.scala-lang:scala3-library_3:3.3.4", + "sha256": "d95184acfcd814da2e051378e4962c653f4b468f4086452ab427af030482bd3c", + }, + "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", + ], + }, + "io_bazel_rules_scala_scala_library_2": { + "artifact": "org.scala-lang:scala-library:2.13.14", + "sha256": "43e0ca1583df1966eaf02f0fbddcfb3784b995dd06bfc907209347758ce4b7e3", + }, + }, + scalatest = True, +) + +scala_register_toolchains() diff --git a/examples/overridden_artifacts/WORKSPACE.bzlmod b/examples/overridden_artifacts/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/examples/scala3/MODULE.bazel b/examples/scala3/MODULE.bazel new file mode 100644 index 000000000..07561dbc6 --- /dev/null +++ b/examples/scala3/MODULE.bazel @@ -0,0 +1,23 @@ +"""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 = "../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) diff --git a/examples/scala3/WORKSPACE.bzlmod b/examples/scala3/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/examples/semanticdb/MODULE.bazel b/examples/semanticdb/MODULE.bazel new file mode 100644 index 000000000..45b36a66f --- /dev/null +++ b/examples/semanticdb/MODULE.bazel @@ -0,0 +1,36 @@ +"""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 = "../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + scala_version = "2.13.15", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) + +#Register and use the custom toolchain that has semanticdb enabled +register_toolchains( + "//:semanticdb_toolchain", +) diff --git a/examples/semanticdb/WORKSPACE.bzlmod b/examples/semanticdb/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/examples/testing/multi_frameworks_toolchain/MODULE.bazel b/examples/testing/multi_frameworks_toolchain/MODULE.bazel new file mode 100644 index 000000000..b27757431 --- /dev/null +++ b/examples/testing/multi_frameworks_toolchain/MODULE.bazel @@ -0,0 +1,76 @@ +"""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 = "../../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +scala_config = use_extension( + "@rules_scala//scala/extensions:config.bzl", + "scala_config", +) +scala_config.settings( + scala_version = SCALA_VERSION, +) +use_repo(scala_config, "io_bazel_rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) + +# Setting `testing = True` would set scalatest, junit, and specs2 to `True`. +scala_deps.toolchains( + scalatest = True, + specs2 = True, +) + +# Under normal circumstances, the above `scala_deps.toolchains()` registration +# 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", + ] +] + +register_toolchains("//:testing_toolchain") diff --git a/examples/testing/multi_frameworks_toolchain/WORKSPACE.bzlmod b/examples/testing/multi_frameworks_toolchain/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/examples/testing/scalatest_repositories/MODULE.bazel b/examples/testing/scalatest_repositories/MODULE.bazel new file mode 100644 index 000000000..3c8a39595 --- /dev/null +++ b/examples/testing/scalatest_repositories/MODULE.bazel @@ -0,0 +1,26 @@ +"""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 = "../../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.toolchains( + scalatest = True, +) diff --git a/examples/testing/scalatest_repositories/WORKSPACE.bzlmod b/examples/testing/scalatest_repositories/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/examples/testing/specs2_junit_repositories/MODULE.bazel b/examples/testing/specs2_junit_repositories/MODULE.bazel new file mode 100644 index 000000000..f5ac371fd --- /dev/null +++ b/examples/testing/specs2_junit_repositories/MODULE.bazel @@ -0,0 +1,26 @@ +"""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 = "../../..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.toolchains( + specs2 = True, +) diff --git a/examples/testing/specs2_junit_repositories/WORKSPACE.bzlmod b/examples/testing/specs2_junit_repositories/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb 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..81e5bbcd6 --- /dev/null +++ b/scala/extensions/config.bzl @@ -0,0 +1,81 @@ +"""Configures core `rules_scala` parameters and exports @io_bazel_rules_scala. + +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 " + + "@io_bazel_rules_scala repository" + ), +) diff --git a/scala/extensions/deps.bzl b/scala/extensions/deps.bzl new file mode 100644 index 000000000..1e12a8861 --- /dev/null +++ b/scala/extensions/deps.bzl @@ -0,0 +1,265 @@ +"""Configures builtin toolchains. + +Provides the `scala_deps` module extension with the following tag classes: + +- `settings` +- `scalafmt` +- `overridden_artifact` +- `compiler_srcjar` +- `toolchains` +- `twitter_scrooge` + +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" + ), + ), +} + +_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" + ), + ), +} + +_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(), +} + +_toolchains_defaults = { + "scalatest": False, + "junit": False, + "specs2": False, + "testing": False, + "scalafmt": False, + "scala_proto": False, + "scala_proto_enable_all_options": False, + "twitter_scrooge": False, + "jmh": False, +} + +_toolchains_attrs = { + "scalatest": attr.bool( + default = _toolchains_defaults["scalatest"], + doc = "Register the Scalatest toolchain", + ), + "junit": attr.bool( + default = _toolchains_defaults["junit"], + doc = "Register the JUnit toolchain", + ), + "specs2": attr.bool( + default = _toolchains_defaults["specs2"], + doc = "Register the Specs2 JUnit toolchain", + ), + "testing": attr.bool( + default = _toolchains_defaults["testing"], + doc = ( + "Register the Scalatest, JUnit, and Specs2 JUnit toolchains " + + "(in place of individual settings)" + ), + ), + "scalafmt": attr.bool( + default = _toolchains_defaults["scalafmt"], + doc = ( + "Register the Scalafmt toolchain; configured by the " + + "`scalafmt` tag" + ), + ), + "scala_proto": attr.bool( + default = _toolchains_defaults["scala_proto"], + doc = "Register the scala_proto toolchain", + ), + "scala_proto_enable_all_options": attr.bool( + default = _toolchains_defaults["scala_proto_enable_all_options"], + doc = ( + "Register the scala_proto toolchain with all options enabled; " + + "`scala_proto` must also be `True` for this to take effect" + ), + ), + "twitter_scrooge": attr.bool( + default = _toolchains_defaults["twitter_scrooge"], + doc = ( + "Use the twitter_scrooge toolchain; configured by the " + + "`twitter_scrooge` tag" + ), + ), + "jmh": attr.bool( + default = _toolchains_defaults["jmh"], + doc = "Use the jmh toolchain", + ), +} + +def _toolchains(mctx): + result = dict(_toolchains_defaults) + + for mod in mctx.modules: + toolchains_tags = mod.tags.toolchains + values = single_tag_values(mctx, toolchains_tags, _toolchains_defaults) + + # Don't overwrite `True` values from one tag with `False` from another. + result.update({k: v for k, v in values.items() if v}) + + return result + +_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 = { + "settings": tag_class( + attrs = _settings_attrs, + doc = "Settings affecting the configuration of all toolchains", + ), + "scalafmt": tag_class( + attrs = _scalafmt_attrs, + doc = "Options for the Scalafmt toolchain", + ), + "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 +""", + ), + "toolchains": tag_class( + attrs = _toolchains_attrs, + doc = ( + "Selects which builtin toolchains to use; the toolchain for the " + + "configured Scala version is always enabled" + ), + ), + "twitter_scrooge": tag_class( + attrs = _twitter_scrooge_attrs, + doc = ( + "Targets that override default `twitter_scrooge` toolchain " + + "dependency providers" + ), + ), +} + +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, + ), + # attr.string_keyed_label_dict isn't available in Bazel 6, and `None` + # breaks attr.string_dict. We can switch after enabling Bazel 8 in #1652. + 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 = "Configures builtin 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..332a3294c --- /dev/null +++ b/scala/private/macros/bzlmod.bzl @@ -0,0 +1,211 @@ +"""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, +) +```py +""" + +def root_module_tags(module_ctx, tag_class_names): + """Returns the bazel_module_tags from the root bazel_module or a fake. + + Returns a fake struct constructed from `tag_class_names` if `module_ctx` + doesn't contain the root module (i.e., the root module doesn't use the + module extension). This is useful for configuring default values in that + case, without having to add special case 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: + The bazel_module_tags from the root bazel_module object if + `module_ctx.modules` contains the root module, + or a struct mapping the specified tag class fields to the empty list + otherwise + """ + 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. + + 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: + `tag_defaults` if `tags` is empty, or a new dict created from the + elements of `tags` + + Raises: + If `tags` contains more than one two tag instances, if both are + `dev_dependency` or regular instances, or if the regular instance + doesn't come first + """ + 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. + + The first key from `attr_dict` identifies the tag field used as the dict + key. 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 of dicts representing unique `tag_name` instance values, using + the first key from `attr_dict` as the key value + + Raises: + if more than one tag instance contains the same key value (i.e., the + same value for the first `attr` in `attr_dict`) + """ + 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/scala_repositories.bzl b/scala/private/macros/scala_repositories.bzl index d6ca48393..974a8dda8 100644 --- a/scala/private/macros/scala_repositories.bzl +++ b/scala/private/macros/scala_repositories.bzl @@ -56,7 +56,7 @@ def _validate_scalac_srcjar(srcjar): oneof = ["url", "urls", "label"] count = 0 for key in oneof: - if key in srcjar: + if srcjar.get(key): count += 1 return count == 1 @@ -94,7 +94,7 @@ def dt_patched_compiler_setup(scala_version, scala_compiler_srcjar = None): ("scala_compiler_srcjar invalid, must be a dict with exactly one of \"label\", \"url\"" + " or \"urls\" keys, got: ") + repr(srcjar), ) - if "label" in srcjar: + if srcjar.get("label"): dt_patched_compiler( name = "scala_compiler_source" + version_suffix(scala_version), build_file_content = build_file_content, 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..ad87195f4 --- /dev/null +++ b/scala/private/macros/test/MODULE.bzlmod_test @@ -0,0 +1,25 @@ +"""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}" +) + +# Remove as part of implementing Bazel 8 compatibility in #1652. +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +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..9a588c3a8 --- /dev/null +++ b/scala/private/macros/test/MODULE.bzlmod_test_root_module @@ -0,0 +1,26 @@ +"""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}" +) + +# Remove as part of implementing Bazel 8 compatibility in #1652. +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) 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.bzl b/scala/toolchains.bzl index 637bce98e..867ef75df 100644 --- a/scala/toolchains.bzl +++ b/scala/toolchains.bzl @@ -60,17 +60,20 @@ def scala_toolchains( Args: maven_servers: Maven servers used to fetch dependency jar files - overridden_artifacts: specific dependency jar files to use instead of - those from `maven_servers`, in the format: + overridden_artifacts: artifacts overriding the defaults for the + configured Scala version, in the format: ```starlark "repo_name": { "artifact": "", "sha256": "", "deps": [ - "repository_names_of_dependencies", + "repository_labels_of_dependencies", ], } ``` + The default artifacts are defined by the + `third_party/repositories/scala_*.bzl` file matching the Scala + version. fetch_sources: whether to download dependency source jars validate_scala_version: whether to check if the configured Scala version matches the default version supported by rules_scala diff --git a/scala/toolchains_repo.bzl b/scala/toolchains_repo.bzl index ff237e36c..35a919ed7 100644 --- a/scala/toolchains_repo.bzl +++ b/scala/toolchains_repo.bzl @@ -102,7 +102,8 @@ _scala_toolchains_repo = repository_rule( "scala_proto_enable_all_options": attr.bool(), "jmh": attr.bool(), "twitter_scrooge": attr.bool(), - # attr.string_keyed_label_dict isn't available in Bazel 6 + # attr.string_keyed_label_dict isn't available in Bazel 6. + # We can switch to it after enabling Bazel 8 in #1652. "twitter_scrooge_deps": attr.string_dict(), }, ) diff --git a/scala_config.bzl b/scala_config.bzl index 70cf132c3..7fe4fd615 100644 --- a/scala_config.bzl +++ b/scala_config.bzl @@ -1,8 +1,7 @@ load("//scala:scala_cross_version.bzl", "extract_major_version", "extract_minor_version", "version_suffix") -def _default_scala_version(): - """return the scala version for use in maven coordinates""" - return "2.12.20" +"""Default Scala version for use in Maven coordinates""" +DEFAULT_SCALA_VERSION = "2.12.20" def _validate_supported_scala_version(scala_major_version, scala_minor_version): if scala_major_version == "2.11" and int(scala_minor_version) != 12: @@ -86,7 +85,7 @@ _config_repository = repository_rule( ) def scala_config( - scala_version = _default_scala_version(), + scala_version = DEFAULT_SCALA_VERSION, scala_versions = [], enable_compiler_dependency_tracking = False): _config_repository( 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..780bcb364 --- /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 = "6.0.2") diff --git a/test/proto_cross_repo_boundary/repo/WORKSPACE.bzlmod b/test/proto_cross_repo_boundary/repo/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/test/shell/test_bzlmod_macros.sh b/test/shell/test_bzlmod_macros.sh new file mode 100755 index 000000000..3a50c9bb1 --- /dev/null +++ b/test/shell/test_bzlmod_macros.sh @@ -0,0 +1,221 @@ +#!/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 + +test_tmpdir="${dir}/tmp/${BASH_SOURCE[0]##*/}" +test_tmpdir="${test_tmpdir%.*}" +mkdir -p "$test_tmpdir" +original_dir="$PWD" +cd "$test_tmpdir" + +teardown_suite() { + # Make sure bazel isn't still running for this workspace. + bazel shutdown + cd "$original_dir" + rm -rf "$test_tmpdir" +} +trap 'teardown_suite' EXIT + +test_srcs_dir="${dir}/scala/private/macros/test" + +setup_test_module() { + # Bazel 6, at least, seems to want external repos to have a `WORKSPACE`. + # Perhaps remove it once we implement Bazel 8 support in #1652. + touch WORKSPACE WORKSPACE.bzlmod + + 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}%${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_tmpdir}/MODULE.bazel:" + ) + + 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}_test_module" + + mkdir -p "$test_module_dir" + cd "$test_module_dir" + setup_test_module + cd "$test_tmpdir" + sed -e "s%\${rules_scala_dir}%${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")" + + rm -rf "$test_module_dir" + assert_matches 'foo bar baz$' "$tag_values" +} + +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_tmpdir}/MODULE.bazel:" + ) + + 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. + +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" diff --git a/test/shell/test_examples.sh b/test/shell/test_examples.sh index 4889f709c..cfe2d7dc5 100755 --- a/test/shell/test_examples.sh +++ b/test/shell/test_examples.sh @@ -67,6 +67,11 @@ function cross_build_example() { test_example examples/crossbuild "bazel build //..." } +function overridden_artifacts_example() { + test_example examples/overridden_artifacts \ + "bazel test --test_output=errors //..." +} + $runner scalatest_repositories_example $runner specs2_junit_repositories_example $runner multi_framework_toolchain_example @@ -77,4 +82,5 @@ $runner scala3_3_example $runner scala3_4_example $runner scala3_5_example $runner scala3_6_example -$runner cross_build_example \ No newline at end of file +$runner cross_build_example +$runner overridden_artifacts_example diff --git a/test/shell/test_helper.sh b/test/shell/test_helper.sh index 11262d33c..47a71b5dc 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 '%b\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_cross_build/MODULE.bazel b/test_cross_build/MODULE.bazel new file mode 100644 index 000000000..68746f870 --- /dev/null +++ b/test_cross_build/MODULE.bazel @@ -0,0 +1,44 @@ +"""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 = "..", +) + +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +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.15", + "3.1.3", + "3.2.2", + "3.3.5", + ], +) +use_repo(scala_config, "io_bazel_rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) +scala_deps.settings( + fetch_sources = True, +) +scala_deps.toolchains( + scalafmt = True, + scalatest = True, +) diff --git a/test_cross_build/WORKSPACE.bzlmod b/test_cross_build/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/test_rules_scala.sh b/test_rules_scala.sh index 554da4735..317ef91a6 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 f88fd3e43..6afbb5c5b 100755 --- a/test_version.sh +++ b/test_version.sh @@ -43,14 +43,19 @@ 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" + scrooge_mod="scrooge_repos.settings($version_param)" fi sed -e "s%\${twitter_scrooge_repositories}%${scrooge_ws}\n%" \ WORKSPACE.template >> $NEW_TEST_DIR/WORKSPACE + sed -e "s%\${twitter_scrooge_repositories}%${scrooge_mod}\n%" \ + MODULE.bazel.template >> $NEW_TEST_DIR/MODULE.bazel + touch $NEW_TEST_DIR/WORKSPACE.bzlmod cp ../.bazel{rc,version} $NEW_TEST_DIR/ cd $NEW_TEST_DIR diff --git a/test_version/MODULE.bazel.template b/test_version/MODULE.bazel.template new file mode 100644 index 000000000..907d01ec5 --- /dev/null +++ b/test_version/MODULE.bazel.template @@ -0,0 +1,72 @@ +"""Bazel module template for //:test_version.sh tests""" + +module(name = "rules_scala_test_version") + +bazel_dep(name = "rules_java", version = "7.12.4") +bazel_dep(name = "rules_proto", version = "6.0.2") +bazel_dep(name = "rules_cc", version = "0.0.9") +single_version_override( + module_name = "rules_cc", + version = "0.0.9", +) + +bazel_dep( + name = "protobuf", + version = "21.7", + repo_name = "com_google_protobuf", +) +single_version_override( + module_name = "protobuf", + version = "21.7", +) + +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, "io_bazel_rules_scala_config") + +scala_deps = use_extension( + "@rules_scala//scala/extensions:deps.bzl", + "scala_deps", +) + +scala_deps.settings( + fetch_sources = True, +) + +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_deps.toolchains( + scala_proto = True, + scalatest = True, + specs2 = True, +) + +register_toolchains( + "@rules_scala//scala:unused_dependency_checker_error_toolchain", + "@twitter_scrooge_test_toolchain//...:all", +) diff --git a/test_version/version_specific_tests_dir/scrooge_repositories.bzl b/test_version/version_specific_tests_dir/scrooge_repositories.bzl index 17e33aa55..d44c27c6e 100644 --- a/test_version/version_specific_tests_dir/scrooge_repositories.bzl +++ b/test_version/version_specific_tests_dir/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/MODULE.bazel b/third_party/test/example_external_workspace/MODULE.bazel new file mode 100644 index 000000000..a6b0871c0 --- /dev/null +++ b/third_party/test/example_external_workspace/MODULE.bazel @@ -0,0 +1,17 @@ +"""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", +) +scala_deps.toolchains( + scalatest = True, +) diff --git a/third_party/test/example_external_workspace/WORKSPACE.bzlmod b/third_party/test/example_external_workspace/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb 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/new_local_repo/WORKSPACE.bzlmod b/third_party/test/new_local_repo/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/third_party/test/proto/MODULE.bazel b/third_party/test/proto/MODULE.bazel new file mode 100644 index 000000000..d8bd75a1a --- /dev/null +++ b/third_party/test/proto/MODULE.bazel @@ -0,0 +1,17 @@ +"""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", +) +scala_deps.toolchains( + scala_proto = True, +) diff --git a/third_party/test/proto/WORKSPACE.bzlmod b/third_party/test/proto/WORKSPACE.bzlmod new file mode 100644 index 000000000..e69de29bb diff --git a/tools/bazel.rc.buildkite b/tools/bazel.rc.buildkite index 5126d6eaf..11137dd31 100644 --- a/tools/bazel.rc.buildkite +++ b/tools/bazel.rc.buildkite @@ -1,5 +1,4 @@ -build --strategy=Scalac=worker --strategy=ScroogeRule=worker --worker_max_instances=3 +# Remove once Bazel 7.5.0 becomes the default supported version. +common --enable_bzlmod -# Remove upon completing Bzlmod compatibility work. -# - https://github.com/bazelbuild/rules_scala/issues/1482 -build --noenable_bzlmod +build --strategy=Scalac=worker --strategy=ScroogeRule=worker --worker_max_instances=3