Skip to content

Commit

Permalink
chore: implement hub-and-spoke for bazel binary downloads (bazel-cont…
Browse files Browse the repository at this point in the history
…rib#128)

- Add `.bcr` folder with requisite files.
- Generate hub repository to aid in the resolution of the Bazel binary
repositories.
- Add `workspace_bazel_binaries` rule to allow repositories that use
`rules_bazel_integration_test` to work with and without bzlmod enabled.

Inspired by bazelbuild/bazel-gazelle#1423.
Related to bazel-contrib#125.
  • Loading branch information
cgrindel committed Sep 27, 2023
1 parent dc402b9 commit d574dbe
Show file tree
Hide file tree
Showing 18 changed files with 251 additions and 19 deletions.
3 changes: 3 additions & 0 deletions .bcr/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fixedReleaser:
login: cgrindel
email: chuck.grindel@gmail.com
15 changes: 15 additions & 0 deletions .bcr/metadata.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"homepage": "https://github.com/bazel-contrib/rules_bazel_integration_test",
"maintainers": [
{
"email": "chuck.grindel@gmail.com",
"github": "cgrindel",
"name": "Chuck Grindel"
}
],
"repository": [
"github:bazel-contrib/rules_bazel_integration_test"
],
"versions": [],
"yanked_versions": {}
}
10 changes: 10 additions & 0 deletions .bcr/presubmit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
bcr_test_module:
module_path: ""
matrix:
platform: ["macos", "ubuntu2004"]
tasks:
run_tests:
name: "Run test module"
platform: ${{ platform }}
test_targets:
- "//examples:simple_test"
5 changes: 5 additions & 0 deletions .bcr/source.template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"integrity": "",
"strip_prefix": "",
"url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/rules_bazel_integration_test.{TAG}.tar.gz"
}
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ filegroup(
":shared_bazelrc_files",
"//bazel_integration_test:all_files",
"//bazel_integration_test/private:all_files",
"//examples:all_files",
"//tools:all_files",
],
visibility = ["//:__subpackages__"],
Expand Down
13 changes: 7 additions & 6 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ register_toolchains("@bazel_tools//tools/python:autodetecting_toolchain")

# GH125: Must keep the Bazel version listed in bazel_versions.bzl in sync with
# those loaded below.
bazel_binaries = use_extension("//:extensions.bzl", "bazel_binaries")
bazel_binaries = use_extension(
"//:extensions.bzl",
"bazel_binaries",
dev_dependency = True,
)
bazel_binaries.download(version_file = "//:.bazelversion")
bazel_binaries.download(version = "7.0.0-pre.20230215.2")
use_repo(
bazel_binaries,
"build_bazel_bazel_.bazelversion",
"build_bazel_bazel_7_0_0-pre_20230215_2",
)
bazel_binaries.rbt_repo(name = "")
use_repo(bazel_binaries, "bazel_binaries")
9 changes: 9 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ buildifier_prebuilt_register_toolchains()

# MARK: - Integration Tests

load("//bazel_integration_test/bzlmod:workspace_bazel_binaries.bzl", "workspace_bazel_binaries")

# This is only necessary while rules_bazel_integration_test switches back and
# forth between WORKSPACE repositories and bzlmod repositories.
workspace_bazel_binaries(
name = "bazel_binaries",
rbt_repo_name = "",
)

load("//:bazel_versions.bzl", "SUPPORTED_BAZEL_VERSIONS")
load("//bazel_integration_test:defs.bzl", "bazel_binaries")

Expand Down
89 changes: 81 additions & 8 deletions bazel_integration_test/bzlmod/bazel_binaries.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,94 @@ load(
)
load("//bazel_integration_test/private:no_deps_utils.bzl", "no_deps_utils")

# MARK: - bazel_binaries_helper Repository Rule

_BAZEL_BINARIES_HELPER_DEFS_BZL = """load("@{rbt_repo_name}//bazel_integration_test/bzlmod:bazel_binary_utils.bzl", "bazel_binary_utils")
def _label(version):
return bazel_binary_utils.label(_VERSIONS, version, lambda x: Label(x))
_VERSIONS = {versions}
bazel_binaries = struct(
label = _label,
)
"""

def _bazel_binaries_helper_impl(repository_ctx):
repository_ctx.file("defs.bzl", _BAZEL_BINARIES_HELPER_DEFS_BZL.format(
versions = repository_ctx.attr.versions,
rbt_repo_name = repository_ctx.attr.rbt_repo_name,
))
repository_ctx.file("WORKSPACE")
repository_ctx.file("BUILD.bazel")

_bazel_binaries_helper = repository_rule(
implementation = _bazel_binaries_helper_impl,
attrs = {
"rbt_repo_name": attr.string(
doc = "The name of the rules_bazel_integration_test repo.",
mandatory = True,
),
"versions": attr.string_dict(
doc = "A mapping of version number/reference to repository name.",
mandatory = True,
),
},
doc = "Hub repository that resolves Bazel versions to Bazel labels.",
)

# MARK: - bazel_binaries Extension

def _declare_bazel_binary(download):
if download.version != "" and download.version_file != None:
fail("""\
A bazel_binary.download can only have one of the following: \
version, version_file.\
""")
if download.version != "":
_bazel_binary_repo_rule(
name = no_deps_utils.bazel_binary_repo_name(download.version),
version = download.version,
)
version = download.version
repo_name = no_deps_utils.bazel_binary_repo_name(version)
_bazel_binary_repo_rule(name = repo_name, version = version)
else:
version = str(download.version_file)
repo_name = no_deps_utils.bazel_binary_repo_name(version)
_bazel_binary_repo_rule(
name = no_deps_utils.bazel_binary_repo_name(str(download.version_file)),
name = repo_name,
version_file = download.version_file,
)
return (version, repo_name)

def _bazel_binaries_impl(module_ctx):
rbt_repo_name = "rules_bazel_integration_test"
version_to_repo = {}
for mod in module_ctx.modules:
for download in mod.tags.download:
_declare_bazel_binary(download)
version, repo_name = _declare_bazel_binary(download)
version_to_repo[version] = repo_name
for rbt_repo in mod.tags.rbt_repo:
rbt_repo_name = rbt_repo.name

_bazel_binaries_helper(
name = "bazel_binaries",
versions = version_to_repo,
rbt_repo_name = rbt_repo_name,
)

_rbt_repo_tag = tag_class(
attrs = {
"name": attr.string(
doc = "The name of the rules_bazel_integration_test repository.",
),
},
doc = """\
For internal use only. Allow a client to specify the name of the \
`rules_bazel_integration_test` repository. The only repository that should use \
this tag class is `rules_bazel_integration_test`.\
""",
)

_download = tag_class(
_download_tag = tag_class(
attrs = {
"version": attr.string(
doc = """\
Expand All @@ -43,9 +108,17 @@ string.\
""",
),
},
doc = """\
Identifies Bazel versions that will be downloaded and made available for \
`bazel_integration_test`.\
""",
)

bazel_binaries = module_extension(
implementation = _bazel_binaries_impl,
tag_classes = {"download": _download},
tag_classes = {"download": _download_tag, "rbt_repo": _rbt_repo_tag},
doc = """\
Provides a means for clients to download Bazel binaries for their integration \
tests.\
""",
)
31 changes: 31 additions & 0 deletions bazel_integration_test/bzlmod/bazel_binary_utils.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Module for resolving repository names for Bazel binaries."""

load("//bazel_integration_test/private:no_deps_utils.bzl", "no_deps_utils")

def _repo_name(version_to_repo_name, version):
if len(version_to_repo_name) == 0:
# Fallback to original behavior.
return no_deps_utils.bazel_binary_repo_name(version)
if no_deps_utils.is_version_file(version):
if not version.startswith("@"):
version = "@@{}".format(version)
version_str = str(Label(version))
else:
version_str = version
repo_name = version_to_repo_name.get(version_str, None)
if repo_name == None:
fail("""\
Failed to find a Bazel binary registered for version '{version}' ({version_str}).\
""".format(
version = version,
version_str = version_str,
))
return repo_name

def _label(version_to_repo_name, version, canonicalize):
repo_name = _repo_name(version_to_repo_name, version)
return canonicalize(no_deps_utils.bazel_binary_label(repo_name))

bazel_binary_utils = struct(
label = _label,
)
30 changes: 30 additions & 0 deletions bazel_integration_test/bzlmod/workspace_bazel_binaries.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Implementation for `workspace_bazel_binaries`."""

_BAZEL_BINARIES_HELPER_DEFS_BZL = """load("@{rbt_repo_name}//bazel_integration_test/private:integration_test_utils.bzl", "integration_test_utils")
bazel_binaries = struct(
label = integration_test_utils.bazel_binary_label,
)
"""

def _workspace_bazel_binaries_impl(repository_ctx):
repository_ctx.file("defs.bzl", _BAZEL_BINARIES_HELPER_DEFS_BZL.format(
rbt_repo_name = repository_ctx.attr.rbt_repo_name,
))
repository_ctx.file("WORKSPACE")
repository_ctx.file("BUILD.bazel")

workspace_bazel_binaries = repository_rule(
implementation = _workspace_bazel_binaries_impl,
attrs = {
"rbt_repo_name": attr.string(
doc = "The name of the rules_bazel_integration_test repo.",
default = "rules_bazel_integration_test",
),
},
doc = """\
Provides a default implementation for a `bazel_binaries` repository. This is \
only necessary for repositories that switch back and forth between WORKSPACE \
repositories and bzlmod repositories.\
""",
)
22 changes: 21 additions & 1 deletion bazel_integration_test/private/bazel_integration_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def bazel_integration_test(
env = {},
env_inherit = _DEFAULT_ENV_INHERIT,
additional_env_inherit = [],
bazel_binaries = None,
**kwargs):
"""Macro that defines a set of targets for a single Bazel integration test.
Expand Down Expand Up @@ -89,13 +90,25 @@ def bazel_integration_test(
values.
additional_env_inherit: Optional. Specify additional `env_inherit`
values that should be passed to the test.
bazel_binaries: Optional for WORKSPACE loaded repositories. Required
for repositories that enable bzlmod. The value for this parameter
is loaded by adding
`load("@bazel_binaries//:defs.bzl", "bazel_binaries")` to your
build file.
**kwargs: additional attributes like timeout and visibility
"""

# To support clients that have not transitioned to bzlmod, we provide a
# bazel_binaries implementation that works in that mode. If a client using
# bzlmod does not specify bazel_binaries, things will go badly for them.
if bazel_binaries == None:
bazel_binaries = struct(
label = integration_test_utils.bazel_binary_label,
)
if bazel_binary == None and bazel_version == None:
fail("The `bazel_binary` or the `bazel_version` must be specified.")
if bazel_binary == None:
bazel_binary = integration_test_utils.bazel_binary_label(bazel_version)
bazel_binary = bazel_binaries.label(bazel_version)

# Find the Bazel binary
bazel_bin_name = name + "_bazel_binary"
Expand Down Expand Up @@ -180,6 +193,7 @@ def bazel_integration_tests(
timeout = "long",
env_inherit = _DEFAULT_ENV_INHERIT,
additional_env_inherit = [],
bazel_binaries = None,
**kwargs):
"""Macro that defines a set Bazel integration tests each executed with a different version of Bazel.
Expand All @@ -205,6 +219,11 @@ def bazel_integration_tests(
values.
additional_env_inherit: Optional. Specify additional `env_inherit`
values that should be passed to the test.
bazel_binaries: Optional for WORKSPACE loaded repositories. Required
for repositories that enable bzlmod. The value for this parameter
is loaded by adding
`load("@bazel_binaries//:defs.bzl", "bazel_binaries")` to your
build file.
**kwargs: additional attributes like timeout and visibility
"""
if bazel_versions == []:
Expand All @@ -224,5 +243,6 @@ def bazel_integration_tests(
timeout = timeout,
env_inherit = env_inherit,
additional_env_inherit = additional_env_inherit,
bazel_binaries = bazel_binaries,
**kwargs
)
2 changes: 1 addition & 1 deletion bazel_integration_test/private/integration_test_utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def _bazel_binary_label(version):
A `string` representing a label for a version of Bazel.
"""
repo_name = no_deps_utils.bazel_binary_repo_name(version)
return "@{repo_name}//:bazel_binary".format(repo_name = repo_name)
return no_deps_utils.bazel_binary_label(repo_name)

def _bazel_integration_test_name(name, version):
"""Generates a test name from the provided base name and the Bazel version.
Expand Down
4 changes: 4 additions & 0 deletions bazel_integration_test/private/no_deps_utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ def _is_version_file(version):
"""
return version.find("//") > -1

def _bazel_binary_label(repo_name):
return "@{repo_name}//:bazel_binary".format(repo_name = repo_name)

no_deps_utils = struct(
bazel_binary_repo_name = _bazel_binary_repo_name,
bazel_binary_label = _bazel_binary_label,
is_version_file = _is_version_file,
normalize_version = _normalize_version,
semantic_version_to_name = _semantic_version_to_name,
Expand Down
10 changes: 9 additions & 1 deletion bazel_integration_test/py/bazel_py_integration_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
load("@rules_python//python:defs.bzl", "py_binary")
load("//bazel_integration_test:defs.bzl", "bazel_integration_tests")

def bazel_py_integration_tests(name, srcs, bazel_versions, main = None, deps = [], env = {}):
def bazel_py_integration_tests(
name,
srcs,
bazel_versions,
main = None,
deps = [],
env = {},
bazel_binaries = None):
# Declare the test runner
runner_name = name
py_binary(
Expand All @@ -19,6 +26,7 @@ def bazel_py_integration_tests(name, srcs, bazel_versions, main = None, deps = [
# Declare the integration tests.
bazel_integration_tests(
name = name,
bazel_binaries = bazel_binaries,
bazel_versions = bazel_versions,
test_runner = runner_name,
env = env,
Expand Down
Loading

0 comments on commit d574dbe

Please sign in to comment.