Skip to content

Commit fab7a74

Browse files
authored
go_sdk: store SDK filenames and hashes in lockfile facts (#4393)
**What type of PR is this?** Feature **What does this PR do? Why is it needed?** This moves the download of the "all versions" JSON, which can't hit the repository cache, from each individual `go_download_sdk` into the module extension. If the current version of Bazel supports facts, this information will also be persisted in the lockfile, allowing for truly airgapped builds assuming an up-to-date download (formerly repository) cache. See bazelbuild/bazel#26198 for the PR that adds facts support to Bazel. **Which issues(s) does this PR fix?** Fixes #3945 **Other notes for review**
1 parent 0d75c3d commit fab7a74

File tree

5 files changed

+75
-35
lines changed

5 files changed

+75
-35
lines changed

go/private/extensions.bzl

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
load("@io_bazel_rules_go_bazel_features//:features.bzl", "bazel_features")
1616
load("//go/private:go_mod.bzl", "version_from_go_mod")
1717
load("//go/private:nogo.bzl", "DEFAULT_NOGO", "NOGO_DEFAULT_EXCLUDES", "NOGO_DEFAULT_INCLUDES", "go_register_nogo")
18-
load("//go/private:sdk.bzl", "detect_host_platform", "go_download_sdk_rule", "go_host_sdk_rule", "go_multiple_toolchains", "go_wrap_sdk_rule")
18+
load("//go/private:sdk.bzl", "detect_host_platform", "fetch_sdks_by_version", "go_download_sdk_rule", "go_host_sdk_rule", "go_multiple_toolchains", "go_wrap_sdk_rule")
1919

2020
def host_compatible_toolchain_impl(ctx):
2121
ctx.file("BUILD.bazel")
@@ -190,6 +190,10 @@ def _go_sdk_impl(ctx):
190190
first_host_compatible_toolchain = None
191191
host_detected_goos, host_detected_goarch = detect_host_platform(ctx)
192192
toolchains = []
193+
194+
sdks_by_version = getattr(ctx, "facts", None) or {}
195+
used_versions = {}
196+
193197
for module in ctx.modules:
194198
# Apply wrapped toolchains first to override specific platforms from the
195199
# default toolchain or any downloads.
@@ -252,18 +256,14 @@ def _go_sdk_impl(ctx):
252256
index = index,
253257
)
254258

255-
# Keep in sync with the other calls to `go_download_sdk_rule` above and below.
256-
go_download_sdk_rule(
259+
_download_sdk(
260+
module_ctx = ctx,
261+
sdks_by_version = sdks_by_version,
262+
used_versions = used_versions,
257263
name = name,
258264
goos = download_tag.goos,
259265
goarch = download_tag.goarch,
260-
sdks = download_tag.sdks,
261-
experiments = download_tag.experiments,
262-
patches = download_tag.patches,
263-
patch_strip = download_tag.patch_strip,
264-
urls = download_tag.urls,
265-
version = download_tag.version,
266-
strip_prefix = download_tag.strip_prefix,
266+
download_tag = download_tag,
267267
)
268268

269269
if (not download_tag.goos or download_tag.goos == host_detected_goos) and (not download_tag.goarch or download_tag.goarch == host_detected_goarch):
@@ -297,18 +297,14 @@ def _go_sdk_impl(ctx):
297297
suffix = "_{}_{}".format(goos, goarch),
298298
)
299299

300-
# Keep in sync with the other calls to `go_download_sdk_rule` above.
301-
go_download_sdk_rule(
300+
_download_sdk(
301+
module_ctx = ctx,
302+
sdks_by_version = sdks_by_version,
303+
used_versions = used_versions,
302304
name = default_name,
303305
goos = goos,
304306
goarch = goarch,
305-
sdks = download_tag.sdks,
306-
experiments = download_tag.experiments,
307-
patches = download_tag.patches,
308-
patch_strip = download_tag.patch_strip,
309-
urls = download_tag.urls,
310-
version = download_tag.version,
311-
strip_prefix = download_tag.strip_prefix,
307+
download_tag = download_tag,
312308
)
313309

314310
toolchains.append(struct(
@@ -370,7 +366,18 @@ def _go_sdk_impl(ctx):
370366
)
371367

372368
if bazel_features.external_deps.extension_metadata_has_reproducible:
373-
return ctx.extension_metadata(reproducible = True)
369+
kwargs = {
370+
"reproducible": True,
371+
}
372+
373+
# See _download_sdk below for details on these facts.
374+
if hasattr(ctx, "facts"):
375+
kwargs["facts"] = {
376+
version: sdk_info
377+
for version, sdk_info in sdks_by_version.items()
378+
if version in used_versions
379+
}
380+
return ctx.extension_metadata(**kwargs)
374381
else:
375382
return None
376383

@@ -400,6 +407,37 @@ def _left_pad_zero(index, length):
400407
fail("index must be non-negative")
401408
return ("0" * length + str(index))[-length:]
402409

410+
def _download_sdk(*, module_ctx, sdks_by_version, used_versions, name, goos, goarch, download_tag):
411+
version = download_tag.version
412+
sdks = download_tag.sdks
413+
if version and not sdks:
414+
# Avoid a download without a known digest in the SDK repo rule by fetching the SDKs filename
415+
# and digest here. When using a version of Bazel that supports module extension facts, this
416+
# info will be persisted in the lockfile, allowing for truly airgapped builds with an
417+
# up-to-date lockfile and download (formerly repository) cache.
418+
if version not in sdks_by_version:
419+
# Lazily fetch the information about all SDKs so that we avoid the download if the facts
420+
# already contain all the versions we care about.
421+
sdks_by_version.clear()
422+
sdks_by_version.update(fetch_sdks_by_version(module_ctx))
423+
if version not in sdks_by_version:
424+
fail("go_sdk: no SDKs found for version {} requested by".format(version), download_tag)
425+
used_versions[version] = True
426+
sdks = sdks_by_version[version]
427+
428+
go_download_sdk_rule(
429+
name = name,
430+
goos = goos,
431+
goarch = goarch,
432+
sdks = sdks,
433+
experiments = download_tag.experiments,
434+
patches = download_tag.patches,
435+
patch_strip = download_tag.patch_strip,
436+
urls = download_tag.urls,
437+
version = download_tag.version,
438+
strip_prefix = download_tag.strip_prefix,
439+
)
440+
403441
go_sdk_extra_kwargs = {
404442
# The choice of a host-compatible SDK is expressed in repository rule attribute values and
405443
# depends on host OS and architecture.

go/private/sdk.bzl

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,7 @@ def _go_download_sdk_impl(ctx):
8181
ctx.report_progress("Finding latest Go version")
8282
else:
8383
ctx.report_progress("Finding Go SHA-256 sums")
84-
ctx.download(
85-
url = [
86-
"https://go.dev/dl/?mode=json&include=all",
87-
"https://golang.google.cn/dl/?mode=json&include=all",
88-
],
89-
output = "versions.json",
90-
)
91-
92-
data = ctx.read("versions.json")
93-
ctx.delete("versions.json")
94-
sdks_by_version = _parse_versions_json(data)
84+
sdks_by_version = fetch_sdks_by_version(ctx)
9585

9686
if not version:
9787
highest_version = None
@@ -582,6 +572,21 @@ def _parse_versions_json(data):
582572
for sdk in sdks
583573
}
584574

575+
def fetch_sdks_by_version(ctx):
576+
ctx.download(
577+
url = [
578+
"https://go.dev/dl/?mode=json&include=all",
579+
"https://golang.google.cn/dl/?mode=json&include=all",
580+
],
581+
output = "versions.json",
582+
)
583+
data = ctx.read("versions.json")
584+
585+
# module_ctx doesn't have delete, but its files are temporary anyway.
586+
if hasattr(ctx, "delete"):
587+
ctx.delete("versions.json")
588+
return _parse_versions_json(data)
589+
585590
def parse_version(version):
586591
"""Parses a version string like "1.15.5" and returns a tuple of numbers or None"""
587592
l, r = 0, 0

tests/bcr/MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ local_path_override(
1919
)
2020

2121
bazel_dep(name = "gazelle", version = "0.33.0")
22+
bazel_dep(name = "platforms", version = "1.0.0")
2223
bazel_dep(name = "protobuf", version = "3.19.6")
2324
bazel_dep(name = "rules_shell", version = "0.4.1")
2425

tests/bcr/WORKSPACE.bzlmod

Whitespace-only changes.

tests/bcr/other_module/MODULE.bazel

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,3 @@ module(name = "other_module")
33
bazel_dep(name = "rules_go", version = "")
44

55
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
6-
7-
# Request an invalid SDK to verify that it isn't fetched since the test module registers a toolchain
8-
# that takes precedence.
9-
go_sdk.download(version = "3.0.0")

0 commit comments

Comments
 (0)