rules_scala
repository·master·Indexed 18 days ago
https://github.com/bazel-contrib/rules_scalaCore Bazel build rules for Scala projects, providing capabilities to build, test, and package Scala software at scale. It includes rules such as scala_library, scala_binary, scala_test, and scala_toolchain, as well as support for SemanticDB, protocol buffers via scala_proto_library, and combined coverage reports using lcov.
What's inside rules_scala
- rules_scala provides core build rules for Scala projects in Bazel. It enables building, testing, and packaging Scala software within the Bazel ecosystem.
Understand Scala dependency modes
masterThe
dependency_modeoption determines which jars are included on the classpath during compilation. This helps balance between strictness and the need to satisfyscalacrequirements.direct: Only includes dependencies explicitly listed in thedepsattribute. This can lead to cryptic errors if a transitive dependency is required byscalacbut not explicitly declared.plus-one: Includesdepsand the immediatedepsof those dependencies. This is the recommended balance, as it prevents most missing dependency errors without significantly increasing incremental build costs.transitive: Includes all transitive dependencies (the entire dependency graph). This is the most permissive but results in the highest incremental build cost.
Caveats for
plus-oneandtransitivemodes:- Extra builds: More dependencies act as inputs to the compilation action, potentially triggering more rebuilds when cross-ijar boundaries change.
- Label propagation: Error messages for target labels may be less precise due to current limitations in how
JavaInfopropagates labels.
How phases and customizable rules work
masterIn
rules_scala, rule implementations are organized into a sequence of phases. Each phase represents a specific step (e.g., compilation, JAR creation).Phases provide two main benefits:
- Modularity: Breaking complex rule logic into smaller, readable groups.
- Customizability: Users can replace default phases with their own (e.g., using a custom Scala compiler) or extend the phase list (e.g., adding a Scala formatting check).
Key Concepts:
- Default Rules: If you don't need customization, simply load
@rules_scala//scala:scala.bzl. - Customizable Rules: Rules prefixed with
make_(e.g.,make_scala_binary) allow users to pass a configuration dictionary to modify the rule's behavior. - Phase Provider:
ScalaRulePhaseis used to pass custom phases into these rules.
How the Phase Architecture works
masterTo provide flexibility,
rules_scalauses a phase architecture. Rule implementations are defined as a sequential list of phases. This allows:- Consumers to define new phases within their workspace to customize rules for specific use cases.
- Contributors to add new default functionality by implementing new phases.
- Clarity in understanding which steps are shared across different rules.
How dependency providers and toolchains work together
masterIn
rules_scala, toolchains provide an indirection layer to configure dependencies (like compiler classpaths) without hardcoding labels. This is achieved through a pattern where dependencies are encapsulated inDepsInfoproviders, which are then attached to a toolchain.The Recommended Pattern: Dependency Providers on Toolchains
When designing rules, the preferred approach is to use dependency providers on toolchains. This is suitable when a rule implementation is 'toolchain aware' and knows how to look up information from a toolchain.
- Define Providers: Use
declare_deps_providerto create targets that hold a list of dependency labels. Each provider is associated with adeps_idused by rules to look up specific dependency sets (e.g.,runtime_depsvscompile_deps). - Create the Toolchain: Use
declare_deps_toolchainto bundle these providers into a toolchain implementation. - Register the Toolchain: Use the standard Bazel
toolchainrule to map your implementation to a specifictoolchain_type.
load("@rules_scala//scala:providers.bzl", "declare_deps_provider") load("@rules_scala//scala/toolchains:toolchains.bzl", "declare_deps_toolchain") # 1. Declare the provider declare_deps_provider( name = "my_compile_deps_provider", deps_id = "compile_deps", visibility = ["//visibility:public"], deps = [ "@com_lihaoyi_fastparse_2_12", "@org_scala_lang_scala_library", ], ) # 2. Declare the toolchain implementation declare_deps_toolchain( name = "my_deps_toolchain_impl", dep_providers = [":my_compile_deps_provider"], visibility = ["//visibility:public"], ) # 3. Register the toolchain toolchain( name = "my_deps_toolchain", toolchain = ":my_deps_toolchain_impl", toolchain_type = "//my_rules/toolchain:my_toolchain_type", visibility = ["//visibility:public"], )- Define Providers: Use
Define custom Scala toolchains without default Scala toolchains
masterIf you are defining your own custom Scala toolchain using
setup_scala_toolchain()(with custom compiler JARs) and do not want to instantiate the default Scala toolchain or compiler JAR repositories, follow these rules:- Bzlmod: Only instantiate the specific tag classes you need from the
scala_depsextension. - WORKSPACE: Set
scala = Falsein thescala_toolchains()call.
This prevents version check failures and avoids unnecessary repository instantiation.
# WORKSPACE: Disable default Scala toolchain scala_toolchains( scala = False, scala_proto = True, twitter_scrooge = True, # ...other toolchain parameters... )- Bzlmod: Only instantiate the specific tag classes you need from the
Access data from previous phases
masterThe second argument
pin a phase function is a global provider. It accumulates information from all previous phases. You can access data from a previous phase using the patternp.<PHASE_NAME>.<FIELD_NAME>.Example: If a previous phase named
jar(withphase_name="jar") returns:return struct( class_jar = class_jar, ijar = ijar, )You can access these values in your current phase via
p.jar.class_jarorp.jar.ijar.How JaCoCo coverage works in rules_scala
masterCoverage is powered by the JaCoCo library, which is managed via
rules_javaandjava_tools.rules_scala$\rightarrow$rules_java$\rightarrow$java_tools$\rightarrow$ JaCoCo.Because
java_toolsandrules_javaare released independently of Bazel, the JaCoCo version may vary depending on yourrules_javaversion. To find your current JaCoCo version, you can inspect thejava_toolsarchive associated with yourrules_javainstallation.Use strict dependency checking
masterThe
strict_deps_moderequires that any type referenced in Scala source code must be explicitly declared in the target'sdeps. This prevents relying on transitive dependencies that might disappear.Modes:
off: No checking.warn: Issues a warning for violations.error: Fails the build on violations.
Handling Violations: When a violation occurs, you will receive an error message suggesting a
buildozercommand to fix it automatically:Target '//some_package:transitive_dependency' is used but isn't explicitly declared, please add it to the deps. You can use the following buildozer command: buildozer 'add deps //some_package:transitive_dependency' //some_other_package:transitive_dependency_userNote: This option only applies to Scala code. Java code in
scala_libraryis still controlled by the standard--strict_java_depsflag.Use unused dependency checking
masterThe
unused_dependency_checker_modeensures that all targets specified indepsare actually used in the code, helping to minimize the classpath and improve build caching.Modes:
off: No checking.warn: Warns about unused dependencies.error: Fails the build on unused dependencies.
Handling Violations: If a dependency is unused, the error message provides a
buildozercommand to remove it:error: Target '//some_package:unused_dep' is specified as a dependency to //target:target but isn't used, please remove it from the deps. You can use the following buildozer command: buildozer 'remove deps //some_package:unused_dep' //target:targetConfiguration:
- This can be enabled globally via a Scala toolchain.
- It can be enabled for individual targets using the
unused_dependency_checker_modeattribute. - If the checker incorrectly flags a target, you can exclude it using the
unused_dependency_checker_ignored_targetsattribute (a list of labels).
Select the Scala version
masterYou can manage Scala versions in
rules_scalausing three different approaches:- Built-in Toolchains: Supports the last two released minor versions for Scala 2.11, 2.12, and 2.13. 2.12 is the default.
- Custom Toolchains: Define your own
scala_toolchainby callingsetup_scala_toolchain()with your specified dependencies. This is the preferred, more flexible method. - Multiple Versions (Cross-compilation): Configure multiple Scala versions and use target-level control to decide which version a specific target uses.
How interface jars (ijar) work in scala_library
masterBy default,
scala_librarysetsbuild_ijar = True. This generates an interface jar that contains only the signatures of the compiled code, not the implementation. This prevents downstream targets from recompiling if you only change the internal implementation of a class without changing its public API.When to disable
build_ijar: If you want to enable inlining of compiled code when it is used as a dependency for another Scala target, you must setbuild_ijar = False. Because anijarcontains no implementation, it cannot be used for inlining.Note for Macros: If you are writing macro code, do not use
scala_librarywithbuild_ijar = False. Instead, use the specificscala_macro_libraryrule.