From 32bb21dae6179f324d6d298bcdf93f9b9682e763 Mon Sep 17 00:00:00 2001 From: erenon Date: Tue, 28 Apr 2026 08:02:43 +0200 Subject: [PATCH 1/2] Watch patch files --- crate_universe/extensions.bzl | 7 +++++++ crate_universe/private/generate_utils.bzl | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/crate_universe/extensions.bzl b/crate_universe/extensions.bzl index 72401bbbdd..f13e8147b3 100644 --- a/crate_universe/extensions.bzl +++ b/crate_universe/extensions.bzl @@ -992,6 +992,13 @@ def _crate_impl(module_ctx): repo_specific_annotations = {} for annotation_tag in mod.tags.annotation: annotation_dict = structs.to_dict(annotation_tag) + + # Watch patch files so changes trigger repository re-generation. + # This must be done before patches are stringified downstream. + for patch_label in annotation_dict.get("patches", []): + if patch_label: + module_ctx.watch(module_ctx.path(patch_label)) + if "build_script_data_select" in annotation_dict: annotation_dict["build_script_data"] = struct( common = annotation_dict["build_script_data"], diff --git a/crate_universe/private/generate_utils.bzl b/crate_universe/private/generate_utils.bzl index 0f104b0d5c..a367460d96 100644 --- a/crate_universe/private/generate_utils.bzl +++ b/crate_universe/private/generate_utils.bzl @@ -289,6 +289,14 @@ def compile_config( if unexpected: fail("The following annotations use `additive_build_file` which is not supported for {}: {}".format(repository_name, unexpected)) + # Watch patch files so changes trigger repository re-generation. + if repository_ctx: + for _name, data in annotations.items(): + patches = data.get("patches", None) + if patches: + for patch in patches: + repository_ctx.watch(repository_ctx.path(Label(patch))) + # Deprecated: Apply `generate_target_compatible_with` to `render_config`. if not generate_target_compatible_with: # buildifier: disable=print From af84345cf64e72f3418026a43d6b0989cf991386 Mon Sep 17 00:00:00 2001 From: erenon Date: Tue, 28 Apr 2026 08:02:52 +0200 Subject: [PATCH 2/2] Add patches and canonical_id from annotations to http_archive --- crate_universe/extensions.bzl | 51 ++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/crate_universe/extensions.bzl b/crate_universe/extensions.bzl index f13e8147b3..51e8195159 100644 --- a/crate_universe/extensions.bzl +++ b/crate_universe/extensions.bzl @@ -549,6 +549,40 @@ def _collect_splicing_config(module, repository): return config +def _get_annotation_patches(module_ctx, annotations, crate_name, crate_version): + """Look up patch-related fields from annotations for a specific crate. + + Args: + module_ctx: The module context for reading files. + annotations: Dict of crate name to list of annotation JSON strings. + crate_name: The name of the crate to look up. + crate_version: The version of the crate to look up. + + Returns: + A tuple of (patches, patch_args, patch_tool, patches_hash). + patches_hash is a hash of patch file contents for cache invalidation. + """ + + crate_annotations = annotations.get(crate_name, []) + for annotation_json in crate_annotations: + version, data = json.decode(annotation_json) + + # Check if version matches ("*" matches all, or exact match, or semver prefix) + if version == "*" or version == crate_version or crate_version.startswith(version): + patches = data.get("patches", None) + patch_args = data.get("patch_args", None) + patch_tool = data.get("patch_tool", None) + if patches or patch_args or patch_tool: + # Compute hash of patch file contents for cache invalidation + patches_hash = None + if patches: + patch_content = "" + for patch in patches: + patch_content += module_ctx.read(module_ctx.path(Label(patch))) + patches_hash = str(hash(patch_content)) + return (patches, patch_args, patch_tool, patches_hash) + return (None, None, None, None) + def _generate_hub_and_spokes( *, module_ctx, @@ -732,15 +766,20 @@ def _generate_hub_and_spokes( version = version.replace("+", "-"), ) + # Look up patches from annotations + ann_patches, ann_patch_args, ann_patch_tool, ann_patches_hash = _get_annotation_patches(module_ctx, annotations, name, version) + if "Http" in repo: # Replicates functionality in repo_http.j2. build_file_content = module_ctx.read(crates_dir.get_child("BUILD.%s-%s.bazel" % (name, version))) repo = repo["Http"] http_archive( name = crate_repo_name, - patch_args = repo.get("patch_args", None), - patch_tool = repo.get("patch_tool", None), - patches = repo.get("patches", None), + # canonical_id forces re-download when patch content changes + canonical_id = ann_patches_hash, + patch_args = ann_patch_args if ann_patch_args else repo.get("patch_args", None), + patch_tool = ann_patch_tool if ann_patch_tool else repo.get("patch_tool", None), + patches = ann_patches if ann_patches else repo.get("patches", None), remote_patch_strip = 1, sha256 = repo.get("sha256", None), type = "tar.gz", @@ -761,9 +800,9 @@ def _generate_hub_and_spokes( git_repository( name = crate_repo_name, init_submodules = True, - patch_args = repo.get("patch_args", None), - patch_tool = repo.get("patch_tool", None), - patches = repo.get("patches", None), + patch_args = ann_patch_args if ann_patch_args else repo.get("patch_args", None), + patch_tool = ann_patch_tool if ann_patch_tool else repo.get("patch_tool", None), + patches = ann_patches if ann_patches else repo.get("patches", None), shallow_since = repo.get("shallow_since", None), remote = repo["remote"], build_file_content = build_file_content,