Skip to content

Commit

Permalink
[build] Support bzlmod for building and installing
Browse files Browse the repository at this point in the history
See https://bazel.build/external/migration for background.

Note that this commit does not pass all tests when bzlmod is enabled;
doing so requires enhancements to our runfiles handling, so for now
bzlmod is still disabled by default (except for CMake installs).

Details:
- Add MODULE.bazel and WORKSPACE.bzlmod for bzlmod use.
  - Note that WORKSPACE.bzlmod is a copy of WORKSPACE with a tweaked
    overview comment and a single functional change to our bzlmod flag.
  - Fix latent bug in repository_excludes for rules_python.
- Adjust install rule so installed paths are "drake" not "_main".
- Disable drake_py add_test_rule pyc files (for newer rules_python).
- Tweak local_config_cc spelling again for bzlmod.
- Add linter for module<->workspace syncing.
- Turn on bzlmod for CMake to achieve test coverage in CI.
  • Loading branch information
jwnimmer-tri committed Dec 5, 2024
1 parent 804b2c6 commit c8456ce
Show file tree
Hide file tree
Showing 22 changed files with 343 additions and 19 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
/build/
/cmake-build-*/

# Bazel build artifacts (symlinks)
# Bazel build artifacts
/bazel-*
/MODULE.bazel.lock

# Platform artifacts generated by `install_prereqs`
/gen/
Expand Down
21 changes: 19 additions & 2 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ exports_files([
"package.xml",
])

exports_files(
[
"MODULE.bazel",
"WORKSPACE",
"WORKSPACE.bzlmod",
],
visibility = ["//tools/workspace:__pkg__"],
)

# A legacy hack module to disambiguate the 'drake' module when Drake is being
# used as a non-bzlmod external. We should remove this when we drop support for
# WORKSPACE (i.e., Bazel >= 9).
Expand Down Expand Up @@ -85,11 +94,19 @@ filegroup(
_INSTALL_TEST_COMMANDS = "install_test_commands"

install(
name = "install",
install_tests_script = _INSTALL_TEST_COMMANDS,
name = "install_files",
data = ["package.xml"],
data_dest = "share/drake",
docs = ["LICENSE.TXT"],
doc_dest = "share/doc/drake",
visibility = ["//visibility:private"],
)

install(
name = "install",
install_tests_script = _INSTALL_TEST_COMMANDS,
deps = [
":install_files",
"//bindings/pydrake:install",
"//common:install",
"//examples:install",
Expand Down
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,10 @@ endforeach()
# however, that the macOS wheel builds also need to know this path, so if it
# ever changes, tools/wheel/macos/build-wheel.sh will also need to be updated.
configure_file(cmake/bazel.rc.in drake_build_cwd/.bazelrc @ONLY)
configure_file(cmake/WORKSPACE.in drake_build_cwd/WORKSPACE.bazel @ONLY)
configure_file(cmake/WORKSPACE.bzlmod.in drake_build_cwd/WORKSPACE.bzlmod @ONLY)
file(CREATE_LINK "${PROJECT_SOURCE_DIR}/.bazeliskrc" drake_build_cwd/.bazeliskrc SYMBOLIC)
file(CREATE_LINK "${PROJECT_SOURCE_DIR}/MODULE.bazel" drake_build_cwd/MODULE.bazel SYMBOLIC)
file(CREATE_LINK "${PROJECT_SOURCE_DIR}/WORKSPACE" drake_build_cwd/WORKSPACE SYMBOLIC)

find_package(Git)

Expand Down
21 changes: 21 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# This file marks a workspace root for the Bazel build system.
# See `https://bazel.build/`.

# This file lists Drake's external dependencies as known to bzlmod.
#
# When bzlmod is disabled, this file is NOT used. Instead, only WORKSPACE is
# used.
#
# When bzlmod is enabled, this file + WORKSPACE.bzlmod are both used, and
# WORKSPACE is ignored.

module(name = "drake")

bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "rules_cc", version = "0.0.17")
bazel_dep(name = "rules_java", version = "8.6.1")
bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "rules_python", version = "0.40.0")

cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension")
use_repo(cc_configure, "local_config_cc")
7 changes: 6 additions & 1 deletion WORKSPACE
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# This file marks a workspace root for the Bazel build system.
# See `https://bazel.build/`.
#
# When bzlmod is disabled, only this file is used. The related files
# MODULE.bazel and WORKSPACE.bzlmod are NOT used.
#
# When bzlmod is enabled, this file is ignored.

workspace(name = "drake")

load("//tools/workspace:default.bzl", "add_default_workspace")

add_default_workspace()
add_default_workspace(bzlmod = False)

load("@build_bazel_apple_support//crosstool:setup.bzl", "apple_cc_configure")

Expand Down
38 changes: 38 additions & 0 deletions WORKSPACE.bzlmod
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- bazel -*-
#
# This file lists Drake's external dependencies as known to bzlmod.
#
# When bzlmod is disabled, this file is NOT used. Instead, only WORKSPACE is
# used.
#
# When bzlmod is enabled, this file + MODULE.bazel are both used, and WORKSPACE
# is ignored.

workspace(name = "drake")

load("//tools/workspace:default.bzl", "add_default_workspace")

add_default_workspace(bzlmod = True)

load("@build_bazel_apple_support//crosstool:setup.bzl", "apple_cc_configure")

apple_cc_configure()

# Add some special heuristic logic for using CLion with Drake.
load("//tools/clion:repository.bzl", "drake_clion_environment")

drake_clion_environment()

load("@bazel_skylib//lib:versions.bzl", "versions")

# This needs to be in WORKSPACE or a repository rule for native.bazel_version
# to actually be defined. The minimum_bazel_version value should match the
# version passed to the find_package(Bazel) call in the root CMakeLists.txt.
versions.check(minimum_bazel_version = "7.1")

# The cargo_universe programs are only used by Drake's new_release tooling, not
# by any compilation rules. As such, we can put it directly into the WORKSPACE
# instead of into our `//tools/workspace:default.bzl` repositories.
load("@rules_rust//crate_universe:repositories.bzl", "crate_universe_dependencies") # noqa

crate_universe_dependencies(bootstrap = True)
1 change: 1 addition & 0 deletions cmake/WORKSPACE.in → cmake/WORKSPACE.bzlmod.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ _BAZEL_WORKSPACE_EXCLUDES = split_cmake_list("@BAZEL_WORKSPACE_EXCLUDES@")
# For anything not already overridden, use Drake's default externals.
add_default_workspace(
repository_excludes = ["python"] + _BAZEL_WORKSPACE_EXCLUDES,
bzlmod = True,
)

load("@build_bazel_apple_support//crosstool:setup.bzl", "apple_cc_configure")
Expand Down
5 changes: 5 additions & 0 deletions cmake/bazel.rc.in
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ startup --output_base="@BAZEL_OUTPUT_BASE@"
# Inherit Drake's default options.
@BAZELRC_IMPORT@

# By default Drake (currently) opts-out of bzlmod, but for CMake builds we want
# to enable it as a mechanism for covering our bzlmod changes in CI, and also
# to be forward-looking since bzlmod is all Bazel >= 9 will support.
common --enable_bzlmod=true

# Environment variables to be used in repository rules (if any).
common @BAZEL_REPO_ENV@

Expand Down
2 changes: 2 additions & 0 deletions tools/bazel.rc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Don't use bzlmod yet.
# TODO(jwnimmer-tri) When we enable bzlmod by default here, we should nix the
# redundant setting drake/cmake/bazel.rc.in at the same time.
common --enable_workspace=true
common --enable_bzlmod=false

Expand Down
15 changes: 15 additions & 0 deletions tools/py_toolchain/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
load("@python//:version.bzl", "PYTHON_BIN_PATH")
load("@rules_python//python:defs.bzl", "py_runtime", "py_runtime_pair")
load(
"@rules_python//python:py_exec_tools_toolchain.bzl",
"py_exec_tools_toolchain",
)
load("//tools/lint:lint.bzl", "add_lint_tests")

py_runtime(
Expand All @@ -19,4 +23,15 @@ toolchain(
toolchain_type = "@rules_python//python:toolchain_type",
)

py_exec_tools_toolchain(
name = "exec_tools",
precompiler = "@rules_python//tools/precompiler:precompiler",
)

toolchain(
name = "exec_tools_toolchain",
toolchain = ":exec_tools",
toolchain_type = "@rules_python//python:exec_tools_toolchain_type",
)

add_lint_tests()
1 change: 1 addition & 0 deletions tools/skylark/drake_py.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def drake_py_binary(
srcs = srcs,
main = main,
deps = deps,
precompile = "disabled", # To avoid *.pyc conflicts.
isolate = isolate,
args = test_rule_args,
data = data + test_rule_data,
Expand Down
20 changes: 20 additions & 0 deletions tools/workspace/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,26 @@ drake_py_test(
deps = [":module_py"],
)

drake_py_test(
name = "workspace_bzlmod_sync_test",
srcs = ["workspace_bzlmod_sync_test.py"],
allow_import_unittest = True,
data = [
":default.bzl",
"//:MODULE.bazel",
"//:WORKSPACE",
"//:WORKSPACE.bzlmod",
"//tools/workspace/bazel_skylib:repository.bzl",
"//tools/workspace/rules_cc:repository.bzl",
"//tools/workspace/rules_license:repository.bzl",
],
tags = ["lint"],
deps = [
":module_py",
"@rules_python//python/runfiles",
],
)

drake_py_binary(
name = "cmake_configure_file",
srcs = ["cmake_configure_file.py"],
Expand Down
9 changes: 6 additions & 3 deletions tools/workspace/bazel_skylib/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# This file exists to make our directory into a Bazel package, so that our
# neighboring *.bzl file can be loaded elsewhere.

load("//tools/lint:lint.bzl", "add_lint_tests")

# Required for workspace_bzlmod_sync_test.py.
exports_files(
["repository.bzl"],
visibility = ["//tools/workspace:__pkg__"],
)

add_lint_tests()
4 changes: 4 additions & 0 deletions tools/workspace/bazel_skylib/repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ def bazel_skylib_repository(name, mirrors = None):
github_archive(
name = name,
repository = "bazelbuild/bazel-skylib",
upgrade_advice = """
When updating, you must also manually propagate to the new version
number into the MODULE.bazel file (at the top level of Drake).
""",
commit = "1.7.1",
sha256 = "e3fea03ff75a9821e84199466799ba560dbaebb299c655b5307f4df1e5970696", # noqa
mirrors = mirrors,
Expand Down
11 changes: 6 additions & 5 deletions tools/workspace/cc/repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ Argument:

load("//tools/workspace:execute.bzl", "execute_or_fail")

# We can probe whether bzlmod is enabled by checking if labels use one or two
# leading '@' charaters. (The label doesn't need to be valid.)
BZLMOD_ENABLED = "@@" in str(Label("//:foo"))

def _check_compiler_version(compiler_id, actual_version, supported_version):
"""
Check if the compiler is of a supported version and report an error if not.
Expand Down Expand Up @@ -91,8 +87,13 @@ def _impl(repository_ctx):
else:
cc_environment = {}

# For Bazel 7.x sometimes we need a weird spelling of @local_config_cc.
# We can probably remove this once our minimum supported Bazel is >= 8.
local_config_cc = "@local_config_cc"
if BZLMOD_ENABLED:
if all([
native.bazel_version.startswith("7."),
"@@" in str(Label("//:foo")),
]):
local_config_cc = "@bazel_tools~cc_configure_extension~local_config_cc"
executable = repository_ctx.path("identify_compiler")
execute_or_fail(repository_ctx, [
Expand Down
31 changes: 28 additions & 3 deletions tools/workspace/default.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,18 @@ load("//tools/workspace/xmlrunner_py:repository.bzl", "xmlrunner_py_repository")
load("//tools/workspace/yaml_cpp_internal:repository.bzl", "yaml_cpp_internal_repository") # noqa
load("//tools/workspace/zlib:repository.bzl", "zlib_repository")

def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS):
# This is the list of modules that our MODULE.bazel already incorporates.
REPOS_ALREADY_PROVIDED_BY_BAZEL_MODULES = [
"bazel_skylib",
"rules_cc",
"rules_license",
"rules_python",
]

def add_default_repositories(
excludes = [],
mirrors = DEFAULT_MIRRORS,
bzlmod = False):
"""Declares workspace repositories for all externals needed by drake (other
than those built into Bazel, of course). This is intended to be loaded and
called from a WORKSPACE file.
Expand All @@ -118,7 +129,11 @@ def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS):
excludes: list of string names of repositories to exclude; this can
be useful if a WORKSPACE file has already supplied its own external
of a given name.
bzlmod: when True, skips repositories declared in our MODULE.bazel;
set this to True if you are using bzlmod.
"""
if bzlmod:
excludes = excludes + REPOS_ALREADY_PROVIDED_BY_BAZEL_MODULES
if "abseil_cpp_internal" not in excludes:
abseil_cpp_internal_repository(name = "abseil_cpp_internal", mirrors = mirrors) # noqa
if "bazelisk" not in excludes:
Expand Down Expand Up @@ -279,6 +294,8 @@ def add_default_repositories(excludes = [], mirrors = DEFAULT_MIRRORS):
rules_license_repository(name = "rules_license", mirrors = mirrors)
if "rules_python" not in excludes:
rules_python_repository(name = "rules_python", mirrors = mirrors)
else:
rules_python_repository(name = "rules_python", _constants_only = True)
if "rules_rust" not in excludes:
rules_rust_repository(name = "rules_rust", mirrors = mirrors)
if "rules_rust_tinyjson" not in excludes:
Expand Down Expand Up @@ -351,13 +368,17 @@ def add_default_toolchains(excludes = []):
native.register_toolchains(
"//tools/py_toolchain:toolchain",
)
native.register_toolchains(
"//tools/py_toolchain:exec_tools_toolchain",
)
if "rust" not in excludes:
register_rust_toolchains()

def add_default_workspace(
repository_excludes = [],
toolchain_excludes = [],
mirrors = DEFAULT_MIRRORS):
mirrors = DEFAULT_MIRRORS,
bzlmod = False):
"""Declare repositories in this WORKSPACE for each dependency of @drake
(e.g., "eigen") that is not explicitly excluded, and register toolchains
for each language (e.g., "py") not explicitly excluded and/or not using an
Expand All @@ -373,5 +394,9 @@ def add_default_workspace(
default values.
"""

add_default_repositories(excludes = repository_excludes, mirrors = mirrors)
add_default_repositories(
excludes = repository_excludes,
mirrors = mirrors,
bzlmod = bzlmod,
)
add_default_toolchains(excludes = toolchain_excludes)
6 changes: 6 additions & 0 deletions tools/workspace/rules_cc/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
load("//tools/lint:lint.bzl", "add_lint_tests")

# Required for workspace_bzlmod_sync_test.py.
exports_files(
["repository.bzl"],
visibility = ["//tools/workspace:__pkg__"],
)

add_lint_tests()
4 changes: 4 additions & 0 deletions tools/workspace/rules_cc/repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ def rules_cc_repository(
github_archive(
name = name,
repository = "bazelbuild/rules_cc", # License: Apache-2.0,
upgrade_advice = """
When updating, you must also manually propagate to the new version
number into the MODULE.bazel file (at the top level of Drake).
""",
commit = "0.0.17",
sha256 = "abc605dd850f813bb37004b77db20106a19311a96b2da1c92b789da529d28fe1", # noqa
patches = [
Expand Down
6 changes: 6 additions & 0 deletions tools/workspace/rules_license/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
load("//tools/lint:lint.bzl", "add_lint_tests")

# Required for workspace_bzlmod_sync_test.py.
exports_files(
["repository.bzl"],
visibility = ["//tools/workspace:__pkg__"],
)

add_lint_tests()
4 changes: 4 additions & 0 deletions tools/workspace/rules_license/repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ def rules_license_repository(
github_archive(
name = name,
repository = "bazelbuild/rules_license", # License: Apache-2.0,
upgrade_advice = """
When updating, you must also manually propagate to the new version
number into the MODULE.bazel file (at the top level of Drake).
""",
commit = "1.0.0",
sha256 = "75759939aef3aeb726e801417a883deefadadb7fea49946a1f5bb74a5162e81e", # noqa
mirrors = mirrors,
Expand Down
Loading

0 comments on commit c8456ce

Please sign in to comment.