Bazel 9.0 is a major long-term-support (LTS) release. It contains new features and backwards incompatible changes.
Highlights
- Bzlmod is now always enabled, and all WORKSPACE logic has been removed from Bazel (#26131), removing age-old tech debt. The Bzlmod migration tool is available to help with your migration.
- All C++-related rules (including cc_binary, cc_library, etc.) have been removed from Bazel and are now located in rules_cc.
- The flag --incompatible_autoload_externally now defaults to the empty string (#23043), meaning that all language-specific rules will now need to be loaded from their respective modules (rules_cc, rules_java, etc). This marks the conclusion of the Starlarkification effort.
- Bazel 9 enforces a minimum dependency on protobuf version 33.4, which includes support for a prebuilt protobuf compiler. Users can set the --@protobuf//bazel/toolchains:prefer_prebuilt_protoc flag to avoid having to build protoc. See the announcement on Protobuf News for more information.
Migration-Ready Incompatible Flags
The following flags are flipped in 9.0:
Users can use bazelisk --migrate with the BAZELISK_INCOMPATIBLE_FLAGS environment variable to test these flag flips with Bazel 8.x.
- --combined_report (now defaults to lcov).
- --experimental_platform_in_output_dir (now defaults to Auto; #27575)
- --experimental_retain_test_configuration_across_testonly (#28075)
- --incompatible_autoload_externally (now defaults to the empty string; #23043)
- --incompatible_bazel_test_exec_run_under (#23179)
- --incompatible_compact_repo_mapping_manifest (#26262)
- --incompatible_disable_autoloads_in_main_repo (#25755)
- --incompatible_disable_native_repo_rules (#22080)
- --incompatible_enable_proto_toolchain_resolution (#28182)
- --incompatible_filegroup_runfiles_for_data (#26330)
- --incompatible_locations_prefers_executable (#27743)
- --incompatible_remove_ctx_bazel_py_fragment (#27080)
- --incompatible_remove_ctx_py_fragment (#27079)
- --incompatible_repo_env_ignores_action_env (#26222)
- --incompatible_strict_action_env (#26587)
- --incompatible_target_cpu_from_platform (#27574)
- --incompatible_use_new_cgroup_implementation (#28244)
- --@bazel_tools//tools/test:incompatible_use_default_test_toolchain (see Configurability / Cquery section below and documentation)
The following flags will be flipped in a future major release:
- --incompatible_eagerly_resolve_select_keys (#26281)
- --incompatible_config_setting_private_default_visibility (#12933)
- --incompatible_disable_starlark_host_transitions (#17032)
- --incompatible_exclude_starlark_flags_from_exec_config (#26909)
General
- [Incompatible] The --watchfs startup option has been removed. Use the --watchfs command option instead.
- [Incompatible] The --verbose_explanations flag is now a no-op: the additional information it produced wasn't useful enough to justify the memory cost of the additional bookkeeping.
- [Incompatible] The --experimental_split_xml_generation flag no longer exists.
- [Incompatible] The long-deprecated --remote_default_platform_properties flag and its --host_platform_remote_properties_override synonym have been removed. Use --remote_default_exec_properties instead.
- [Incompatible] The --incompatible_sandbox_hermetic_tmp flag has been removed. To mount the entire /tmp directory into the sandbox, specify --sandbox_add_mount_pair=/tmp.
- [Incompatible] The --run_env flag now has lower priority than the env attribute on specific targets. This is in line with existing behavior of other flags like --test_env.
- [Incompatible] When --modify_execution_info is specified multiple times, the individual values are now all interpreted in order rather than only the value specified last.
- The "bazel --quiet" startup option can now be used to make Bazel emit much less output.
- Commands executed by "bazel run" now have two extra environment variables: $BUILD_ID indicates the id of the Bazel command and $BUILD_EXECROOT indicates the execroot of the Bazel server.
- The progress of completed configured aspects is now shown in the UI, next to the number of completed targets. Example: "(100 packages loaded, 10000 targets configured, 500 aspect applications)".
- --max_idle_secs now takes system sleep time into account when deciding when to shutdown the Bazel server.
- Soft-deprecated bazel startup option --write_command_log. Users should switch to use the command option with the same name or look at the build event messages.
- Added --verbose_visibility_errors for printing more information when a visibility violation occurs.
- The new --*_env==NAME syntax can be used with any of --action_env, --host_action_env, --repo_env, --run_env, and --test_env to undo any previous occurrences of the respective flags for that environment variable name. For --repo_env and --run_env, this also results in the variable being unset if it is set in the environment of the Bazel client.
- The --experimental_cancel_concurrent_tests option now accepts the values on_passed, on_failed and never and cancels concurrent test runs on the first matching result. If enabled, it's now effective by default and no longer requires --test_strategy=standalone to be passed explicitly.
- "bazel info install_md5" is now available to tell the logical checksum of the Bazel server. This is mostly intended as a debugging aid.
- Added support for setting the .bazelrc path via a BAZELRC environment variable. The environment variable can include multiple comma-separated paths.
- bazel query/cquery/aquery now supports an executables() function to find only the executable targets in a given expression.
- Bazel is now able to periodically garbage-collect the persistent action cache while the server is idle. This can be configured through the --experimental_action_cache_gc_max_age, --experimental_action_cache_gc_threshold and --experimental_action_cache_gc_idle_delay flags.
- Similar to the above, stale install bases will also be garbage-collected (controlled by --experimental_install_base_gc_max_age).
- Bazel will now re-install on detection of a corrupted installation instead of giving up.
- aquery now supports --output=commands.
Build Event Protocol
- The gRPC downloader now sends Fetch events to the BES.
- Added a new flag
--incompatible_bep_cpu_from_platform(defaults to false). When set to true, the values ofConfiguration.platform_nameandConfiguration.cpuwill be obtained from--experimental_override_platform_cpu_namemapping for the target platform or the CPU constraint of the platform if a mapping does not exist. - The BEP now includes the critical path time (critical_path_time in TimingMetrics).
C++ / Objective-C
- [Incompatible] As noted in the Highlights section, all C++ rules have been removed from Bazel and must be loaded from @rules_cc. These include cc_binary, cc_import, cc_library, cc_shared_library, cc_static_library, cc_test, cc_toolchain, cc_toolchain_alias, objc_import, objc_library, fdo_prefetch_hints, fdo_profile, memprof_profile, propeller_optimize.
- [Incompatible] Removed the --incompatible_enable_cc_test_feature flag. The functionality this was intended for was never completed, and is no longer needed.
- [Incompatible] linking_context.linkstamps has been removed.
- [Incompatible] Bazel now uses -I instead of -isystem for cc_library / cc_binary includes attr. To use -isystem for only external repositories, you can pass --features=external_include_paths --host_features=external_include_paths. To use -isystem for a single cc_library / cc_binary includes, you can set features = ["system_include_paths"] on the target.
- Tool paths specified in cc_toolchain action configs are now normalized based on the current execution platform's OS rather than the host OS. In particular, Windows-style absolute paths are now treated as absolute paths when building on a Windows executor from a non-Windows host.
- Introductory support for C++20 modules; it supports cc_library with a single module interface.
Configurability / cquery
- [Incompatible] Constraints and toolchain requirements on the default exec group no longer apply to the test exec group, which configures test actions. This includes, for example, the rule() function's toolchains parameter and exec_compatible_with. To configure test actions, use exec_group_compatible_with or define an explicit test exec group with toolchain requirements on test rules that require it.
- [Incompatible] Changing --test_env no longer invalidates the analysis cache. ctx.configuration.test_env may be empty for non-test rules and should not be used by them.
- [Incompatible] Python rules can no longer set runtimes with --python_top (--incompatible_use_python_toolchains=false no longer works). See https://rules-python.readthedocs.io/en/latest/toolchains.html for toolchain & runtime configuration guidance.
- [Incompatible] --incompatible_bazel_test_exec_run_under is now enabled by default, which means that the --run_under target in a bazel test invocation will be built for each test's execution platform.
- The new --@bazel_tools//tools/test:incompatible_use_default_test_toolchain flag means test actions select an execution platform that matches the target platform's constraints instead of always selecting the first registered execution platform. This is enabled by default and supersedes --use_target_platform_for_tests. See #25823 for details and migration advice.
- The new flag --allowed_strategies_by_exec_platform restricts execution strategies based on the execution platform. For example, this can be used to ensure host platform-configured targets only run on local Mac machines instead of remote Linux machines. See #24265.
- Starlark flag definitions can set scope = "target" to reset to default values in exec configurations. This improves build performance. scope = "universal" persists their values into exec configs.
- The new exec_group_compatible_with attribute lets build targets add additional constraints for selecting execution platforms for actions in custom execution groups.
- The new config.none() transition supports modeling data dependencies that should always be the same regardless of their parent configuration.
- select() on cpu, host_cpu, or crosstool_top now emits a "deprecated flag" warning.
- toolchain_type's new no_match_error attribute provides a custom message when toolchain resolution fails to find a matching toolchain but one is required.
- platform's new missing_toolchain_error attribute provides custom messages when a required toolchain type cannot be found for a platform.
- The bazel config command now reports which configurations had test-related flags removed (this happens automatically for non-"*_test" targets).
- The new ctx.configuration.short_id field provides a short identifier for the current configuration. This can be used with bazel config to show the configuration's flags.
- Setting the same aspect multiple times with multiple --aspects flags is no longer an error; they're simply deduplicated.
Coverage
- [Incompatible] Baseline coverage files for individual tests are no longer announced in the BES. Instead, a new baseline_report.lcov file containing the merged baseline coverage information for the entire invocation is now announced in the BES. It is also still merged into the combined report.
- Baseline coverage files are now included in the combined report.
- LCOV parsing does not break on FN lines including an end line number.
- Rules can now register their own baseline coverage files in LCOV format via the new baseline_coverage_files parameter of ctx.instrumented_files_info. If the target matches the instrumentation filter, Bazel will merge the data into the combined coverage report generated with --combined_report.
- Branches will always be merged as best as possible based on branch and block numbers during coverage report generation.
External Dependencies
- [Incompatible] As noted in the Highlights section, Bzlmod is now always enabled, and all WORKSPACE logic has been removed from Bazel (#26131). The --enable_bzlmod and --enable_workspace flags are now no-ops.
- [Incompatible] The bazel sync command has been removed. Use bazel fetch --all instead.
- [Incompatible] The Python six library is no longer included in @bazel_tools.
- [Incompatible] The legacy @bazel_tools//tools/build_defs/repo:maven_rules.bzl rule for downloading Maven artifacts has been deleted. Consider migrating to rules_jvm_external if you are using this rule.
- [Incompatible] The canonical names of repos created with use_repo_rule have changed to be more stable. This may require updating command-line flags such as --override_repository if you're using canonical repo names; however, note that --override_repository now also supports apparent repo names (see below).
- [Incompatible] --experimental_worker_for_repo_fetching is removed.
- [Incompatible] A single_version_override that pins a module to a lower version than requested in a bazel_dep for that module now results in an error instead of silently ignoring the bazel_dep version requirement. This is meant to catch a common source of bugs when updating a bazel_dep without noticing that it is overridden.
- [Incompatible] Added and flipped --incompatible_compact_repo_mapping_manifest, which causes the repo mapping manifest file for runfiles to use a more compact format when necessary.
- [Incompatible] With --experimental_check_external_repository_files enabled (the default), Bazel will now refetch the respective repositories when it encounters external modifications. This is necessary to ensure correct incrementality. If you rely on external modifications to these repositories, either disable the flag or use a supported mechanism such as --override_repository, local_path_override or override_repo.
- --override_repository now supports apparent repository names from the point of view of the main repository. An unknown apparent repo name will result in an error.
- Added a new flag --repo_contents_cache (defaults to the contents directory under the --repository_cache) where Bazel stores fetched contents of repos that can be safely cached across workspaces. A repo rule can indicate cacheability by returning repository_ctx.repo_metadata(reproducible=True) from its implementation function.
- Module extensions can store a JSON-like Starlark object in module_ctx.extension_metadata(facts = ...) and retrieve it back in future evaluations of the extension via module_ctx.facts without any invalidation taking place.
- bazel mod show_repo now has two new flags: --all_repos to show all defined repos; and --all_visible_repos to show all repos visible to the base module (--base_module, defaults to the root module), along with their apparent names.
- External repositories that are managed by Bzlmod can now contain a top-level external directory or package.
- REPO.bazel now allows another directive, "ignore_directories()". It takes a list of directories to ignore just like .bazelignore does, but with glob semantics.
- Added repository_ctx.original_name, which contains the original value of the name attribute as specified at the repo rule call site.
- repository_ctx.execute can now remove an environment variable when executing a process by associating it with the value None in the environment argument.
- bazel mod now tries to evaluate all module extensions, even when some have failed to evaluate.
- The new --inject_repository flag can be used to add new repositories via the CLI with --enable_bzlmod. Such repositories behave as if they were declared by local_repository via use_repo_rule in the root module.
- archive_override now accepts all attributes usable with http_archive; similar for git_override and git_repository.
- Fixed an issue where genquery in external repos would evaluate labels as if they were in the main repo.
- --experimental_downloader_config is now no longer experimental, and has been renamed to --downloader_config. The old flag name can still be used.
- The stripPrefix parameter of repository_ctx.download_and_extract() and repository_ctx.extract() has been renamed to strip_prefix; the deprecated stripPrefix name remains usable for compatibility.
- User-provided repo names may now start with a number.
- File change checks for non-output, non-repo external files can now be disabled with the --experimental_check_external_other_files flag.
- Added the load_wasm and execute_wasm methods to repository_ctx and module_ctx that allow repo rules and module extensions to run a WebAssembly binary. These methods are only available if --experimental_repository_ctx_execute_wasm is set.
- repository_ctx.download_and_extract now supports the .whl file extension for Python wheel files, treating them as ZIP archives under PEP 427.
- Modules backed by http_archive or git_repository no longer require a MODULE.bazel file to be contained in the source archive.
- The new --module_mirrors flag accepts a comma-separated list of mirrors to use for source URLs provided by modules obtained from Bazel registries. It also supports specifying mirrors for individual registries via the syntax --module_mirrors==[,,...].
- native.existing_rule() and native.existing_rules() now correctly handle labels pointing to a different repo.
- The results of reproducible repository rules without dependencies added at runtime (e.g., via repository_ctx.watch or .getenv) can now be cached in a regular HTTP or gRPC remote cache if the new --experimental_remote_repo_contents_cache startup option is provided.
- In environment variable values set via --repo_env, the substring %bazel_workspace% is now replaced with the absolute path of the current Bazel workspace. This can, for example, be used to make tools checked into the repository available on the PATH for repository rules.
- git_repository and new_git_repository can now check out the repository's default branch if the branch, tag, and commit attributes are all omitted.
- The @bazel_tools//tools/zip:zipper tool now supports empty filelists, creating an empty archive instead of throwing an error.
Java
- [Incompatible] --experimental_java_classpath now defaults to bazel (was: javabuilder). This is expected to reduce the number of Java compilations running on incremental builds if a remote or disk cache is used. Other types of builds are not expected to be negatively affected by this change.
- Java tests are no longer run with a SecurityManager that prevents System.exit, since SecurityManager functionality is being removed from the JDK. System.exit calls are still reported and fail the test.
- java_import.jars attributes can no longer be empty, and --noincompatible_disallow_java_import_empty_jars is no longer supported.
- singlejar can exclude certain entries.
- If a java_import target's srcjar attribute refers to a source file, the file must exist.
Local Execution
- [Incompatible] The mnemonic passed to --worker_extra_flag is now matched against the worker key mnemonic when one is available, instead of the action mnemonic. This makes it consistent with other worker flags taking a mnemonic.
- On Linux, the default limit on the number of --watchfs file events per directory has been raised to 10,000 (from 500). If needed, it can be increased further via --host_jvm_args=-Djdk.nio.file.WatchService.maxEventsPerPoll=.
- Copying outputs out of the sandbox has been optimized and is now up to 80% faster for tree artifacts with a large number of children.
- Added a new flag, --sandbox_enable_loopback_device (defaults to true). If set to false, a loopback device will not be set up in the linux-sandbox network namespace for local actions.
- Added a new startup flag --experimental_run_in_user_cgroup (defaults to false). If set to true, the client creates a temporary script file called
systemd-wrapper.sh, and the daemonize binary will run this script withsystemd-runinstead of running the Java server directly. This flag only works on Linux.
Performance
- Actions that create runfiles trees are now considered regular actions. This means that they are now reported in statistics, critical paths and the like.
- Changing any part of --run_under that isn't the label (such as the shell command) no longer invalidates the analysis cache.
- Added flag --experimental_thread_dump_interval to allow Bazel dump threads periodically. This is especially helpful to debug hangs where Bazel runs unsupervised, e.g. on CI.
- The contents of source directories are now tracked for invalidation. Using glob or explicit lists of files to consume source directories is still strongly preferred, but there may be cases in which this isn't feasible (e.g. file names that aren't valid labels).
- NestedSet memoization has been replaced with weakly referenced lists, resulting in a reduction of up to 20% retained heap and 15% peak heap.
- Added a new flag --gc_churning_threshold (an integer from 0 to 100; defaults to 100) to tell Bazel to give up if more than this percentage of wall time is spent performing GC. Another new flag, --gc_churning_threshold_if_multiple_top_level_targets which takes precedence if there are multiple top-level targets.
Remote Execution
- [Incompatible] Flag --incompatible_remote_use_new_exit_code_for_lost_inputs is deleted.
- [Incompatible] When remote execution fails and an action is executed locally, modifications of its inputs during execution are now checked according to the value of the --guard_against_concurrent_changes flag rather than as if that flag was set to full.
- The Merkle tree implementation used by remote caching and execution has been reworked, resulting in up to a 30% wall time and 70% peak heap reduction.
- The new flag --remote_max_concurrency_per_connection can be used to specify the maximum number of concurrent gRPC requests Bazel will issue on a single connection to the server. The default value of 100 matches the previous behavior.
- Directories matched by --remote_download_regex will now be fully downloaded. Previously, directory-only matches would result in an empty directory.
- Added support for Zstd, Deflate and Snappy encoding to HTTP remote caches.
- Setting --remote_grpc_log= (the empty string) will now disable the remote gRPC log (previously it would throw an error).
Starlark / Build Language
- Global functions and built-in data types:
- [Incompatible] Starlark range() function, the string split() and rsplit() methods, and list index() and pop() methods no longer accept None as argument values.
- The string split() and rsplit() methods now allow sep and maxsplit to be provided as keyword arguments.
- (Also in 8.1.0+) Added a set data type to Starlark.
- Rules API:
- [Incompatible] Legacy struct providers are no longer supported. The --incompatible_disallow_struct_provider_syntax flag is now a no-op.
- [Incompatible] Strings in attribute's "provides" parameters are no longer supported.
- The new attr.label_list_dict type accepts a dict in which keys are strings and values are lists of labels.
- Turn the vestigial distribs attribute into a no-op.
- The (deprecated) output_licenses attribute is now a string list rather than an internal data type. The only visible change may be that some query output may change from printing capitalized values of inputs will now show lower case.
- Materializer functions now have access to the label of the rule they are running on as ctx.label.
- Adds ctx.rule.var to allow aspects to get rule-specific variables, and removes rule-specific variables from an aspect's ctx.var dict.
- The mnemonic of a file write action can now be set via the mnemonic parameter of ctx.actions.write.
- ctx.actions.write now supports path mapping when passed an Args object. Use the mnemonics attribute to assign it a dedicated mnemonic, which can then be used with --modify_execution_info to opt in to path mapping (see #22658 for details on path mapping).
- ctx.actions.symlink now accepts a target_type argument.
- Symbolic macros:
- [Incompatible] Starlark computation step limits are now enforced for symbolic macros.
- Added inherit_attrs param to macro() to allow symbolic macros to inherit attributes from rules or other symbolic macros.
- Set generator_name, generator_function, generator_location, and the full Starlark stack for rule targets instantiated in a symbolic macro.
- Starlark API documentation:
- The starlark_doc_extract output now includes the set of allowed values for string or integer attributes.
- The starlark_doc_extract output now supports symbolic macro visibility, attribute inheritance, and rule finalizers.
- Remove the non-existent "name" attribute from starlark_doc_extract output for aspects.
- Stop documenting the vestigial distribs attribute.
- OSS Bazel permits but ignores type annotations in .bzl files. Type annotations are experimental, and code that uses them may fail in future versions of Bazel.
Windows
- If a cc toolchain feature named shorten_virtual_includes is enabled, virtual include header files are linked under bin/_virtual_includes/ instead of bin//_virtual_includes/. With rules_cc 0.1.3 or later, this shortens the virtual include paths which is critical for mitigating long path issues with MSVC on Windows.
- Tool paths specified in cc_toolchain action configs are now normalized based on the current execution platform's OS rather than the host OS. In particular, Windows-style absolute paths are now treated as absolute paths when building on a Windows executor from a non-Windows host.
- ctx.actions.symlink with a target_file argument will now create a symlink or junction as appropriate for the target artifact type (file or directory), even when building without the bytes; with a target_path argument, the type may be manually specified via the new target_type argument.
- Bazel now falls back gracefully instead of crashing on Windows filesystems that do not support junctions (such as virtiofs or VirtualBox shared folders).
Appendix
The following flags have been removed:
- --experimental_add_exec_constraints_to_targets
- --experimental_split_xml_generation
- --experimental_worker_for_repo_fetching
- --incompatible_enable_cc_test_feature
- --incompatible_remote_use_new_exit_code_for_lost_inputs
- --incompatible_sandbox_hermetic_tmp
- --verbose_explanations
- --watchfs (startup option)
The following flags are now no-ops:
- --enable_bzlmod
- --enable_workspace
- --experimental_bep_target_summary
- --incompatible_disable_native_repo_rules
- --incompatible_legacy_local_fallback
- --incompatible_use_python_toolchains
- --legacy_external_runfiles
- --verbose_explanations
Acknowledgements:
This release contains contributions from many people at Google, as well as Aaron Sky, Adin Cebic, Adrian Vogelsgesang, Agustin Mista, Alan Mond, Alberto Cavalcante, Alessandro Patti, Alex Eagle, Alex Fax, Alexander Golovlev, Ankush Chudiwal, Austin Schuh, Benjamin Peterson, Benji Vos, Benson Muite, Bo Zhang, Boleyn Su, Bradley Bridges, Chris Sauer, Christian Scott, Christopher Rydell, Chuck Grindel, Cornelius Riemenschneider, David Sanderson, David Zbarsky, DeeperMind, Dimi Shahbaz, Dmitry Ivankov, Dmitry Ryabkov, Ed Schouten, Eric Riff, Fabian Meumertzheim, Farid Zakaria, Fredrik Medley, George Gensure, Grzegorz Lukasik, Han-Wen Nienhuys, Honnix, Ian Stapleton Cordasco, jacqueline.lee, Jaden Peterson, James Jenkins, James Judd, Javier Maestro, Jay Conrod, Jim Carroll, jjudd, John Millikin, Jon Shea, Jonathan Schear, Jonathan Woodbury, Jordan Mele, kalvdans, Keith Lea, Keith Smiley, Kevin Bernal, Kiron, kxxt, Lev Leontev, László Csomor, Lucas Loffel, Luis Padron, Marcus Eagan, Mark Reuter, Markus Hofbauer, Matt Brown, Matt Smith, Mike Lundy, Mislav Mandaric, Nathan Naze, nialdaly, Pareesh Madan, Pelikan.B, Peter Li, Peter Lukacs, Philipp Stephani, PikachuHy, Ricard Sol, Richard Woodbury, Roman Salvador, Rostislav Rumenov, Ruoyu Zhong, Ryan Matthews, Sara Adams, sarad, Simon Thornington, Siva Mahadevan, Son Luong Ngoc, Spencer Putt, Stephan Pleines, Steve Barrau, Taylor Barrella, tfrench, Thi Don, Timothy Gu, Tomasz Pasternak, Torgil Svensson, Ulrik Falklof, Valentin Grigorev, Vy Hong, Wade Carpenter, Will Stranton, Wojciech Mazur, xndcn, Yannic Bonenberger, Zachary Kipping, and Zhongpeng Lin.
Notice: Bazel installers contain binaries licensed under the GPLv2 with Classpath exception. Those installers should always be redistributed along with the source code.
Some versions of Bazel contain a bundled version of OpenJDK. The license of the bundled OpenJDK and other open-source components can be displayed by running the command bazel license. The vendor and version information of the bundled
OpenJDK can be displayed by running the command bazel info java-runtime. The binaries and source-code of the bundled OpenJDK can be downloaded from our mirror server.
Security: All our binaries are signed with our public key 3D5919B448457EE0.