github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/internal/bzlmod/go_deps.bzl (about) 1 # Copyright 2023 The Bazel Authors. All rights reserved. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 15 load("//internal:go_repository.bzl", "go_repository") 16 load( 17 ":default_gazelle_overrides.bzl", 18 "DEFAULT_BUILD_EXTRA_ARGS_BY_PATH", 19 "DEFAULT_BUILD_FILE_GENERATION_BY_PATH", 20 "DEFAULT_DIRECTIVES_BY_PATH", 21 ) 22 load(":go_mod.bzl", "deps_from_go_mod", "go_work_from_label", "sums_from_go_mod", "sums_from_go_work") 23 load(":semver.bzl", "COMPARES_HIGHEST_SENTINEL", "semver") 24 load( 25 ":utils.bzl", 26 "drop_nones", 27 "extension_metadata", 28 "format_rule_call", 29 "get_directive_value", 30 "with_replaced_or_new_fields", 31 ) 32 33 visibility("//") 34 35 _HIGHEST_VERSION_SENTINEL = semver.to_comparable("999999.999999.999999") 36 37 _FORBIDDEN_OVERRIDE_TAG = """\ 38 Using the "go_deps.{tag_class}" tag in a non-root Bazel module is forbidden, \ 39 but module "{module_name}" requests it. 40 41 If you need this override for a Bazel module that will be available in a public \ 42 registry (such as the Bazel Central Registry), please file an issue at \ 43 https://github.com/bazelbuild/bazel-gazelle/issues/new or submit a PR adding \ 44 the required directives to the "default_gazelle_overrides.bzl" file at \ 45 https://github.com/bazelbuild/bazel-gazelle/tree/master/internal/bzlmod/default_gazelle_overrides.bzl. 46 """ 47 48 _GAZELLE_ATTRS = { 49 "build_file_generation": attr.string( 50 default = "on", 51 doc = """One of `"auto"`, `"on"` (default), `"off"`. 52 53 Whether Gazelle should generate build files for the Go module. 54 55 Although "auto" is the default globally for build_file_generation, 56 if a `"gazelle_override"` or `"gazelle_default_attributes"` tag is present 57 for a Go module, the `"build_file_generation"` attribute will default to "on" 58 since these tags indicate the presence of `"directives"` or `"build_extra_args"`. 59 60 In `"auto"` mode, Gazelle will run if there is no build file in the Go 61 module's root directory. 62 63 """, 64 values = [ 65 "auto", 66 "off", 67 "on", 68 ], 69 ), 70 "build_extra_args": attr.string_list( 71 default = [], 72 doc = """ 73 A list of additional command line arguments to pass to Gazelle when generating build files. 74 """, 75 ), 76 "directives": attr.string_list( 77 doc = """Gazelle configuration directives to use for this Go module's external repository. 78 79 Each directive uses the same format as those that Gazelle 80 accepts as comments in Bazel source files, with the 81 directive name followed by optional arguments separated by 82 whitespace.""", 83 ), 84 } 85 86 def _fail_on_non_root_overrides(module_ctx, module, tag_class): 87 if module.is_root: 88 return 89 90 # Isolated module extension usages only contain tags from a single module, so we can allow 91 # overrides. This is a new feature in Bazel 6.3.0, earlier versions do not allow module usages 92 # to be isolated. 93 if getattr(module_ctx, "is_isolated", False): 94 return 95 96 if getattr(module.tags, tag_class): 97 fail(_FORBIDDEN_OVERRIDE_TAG.format( 98 tag_class = tag_class, 99 module_name = module.name, 100 )) 101 102 def _fail_on_duplicate_overrides(path, module_name, overrides): 103 if path in overrides: 104 fail("Multiple overrides defined for Go module path \"{}\" in module \"{}\".".format(path, module_name)) 105 106 def _fail_on_unmatched_overrides(override_keys, resolutions, override_name): 107 unmatched_overrides = [path for path in override_keys if path not in resolutions] 108 if unmatched_overrides: 109 fail("Some {} did not target a Go module with a matching path: {}".format( 110 override_name, 111 ", ".join(unmatched_overrides), 112 )) 113 114 def _check_directive(directive): 115 if directive.startswith("gazelle:") and " " in directive and not directive[len("gazelle:"):][0].isspace(): 116 return 117 fail("Invalid Gazelle directive: \"{}\". Gazelle directives must be of the form \"gazelle:key value\".".format(directive)) 118 119 def _get_override_or_default(specific_overrides, gazelle_default_attributes, default_path_overrides, path, default_value, attribute_name): 120 # 1st: Check for user-provided specific overrides. If a specific override is found, 121 # all of its attributes will be applied (even if left to the tag's default). This is to allow 122 # users to override the gazelle_default_attributes tag back to the tag's default. 123 # 124 # This will also cause "build_file_generation" to default to "on" if a specific override is found. 125 specific_override = specific_overrides.get(path) 126 if specific_override and hasattr(specific_override, attribute_name): 127 return getattr(specific_override, attribute_name) 128 129 # 2nd. Check for default attributes provided by the user. This must be done before checking for 130 # gazelle's defaults path overrides to prevent Gazelle from overriding a user-specified flag. 131 # 132 # This will also cause "build_file_generation" to default to "on" if default attributes are found. 133 global_override_value = getattr(gazelle_default_attributes, attribute_name, None) 134 if global_override_value: 135 return global_override_value 136 137 # 3rd: Check for default overrides for specific path. 138 default_path_override = default_path_overrides.get(path) 139 if default_path_override: 140 return default_path_override 141 142 # 4th. Return the default value if no override was found. 143 # This will cause "build_file_generation" to default to "auto". 144 return default_value 145 146 def _get_directives(path, gazelle_overrides, gazelle_default_attributes): 147 return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_DIRECTIVES_BY_PATH, path, [], "directives") 148 149 def _get_build_file_generation(path, gazelle_overrides, gazelle_default_attributes): 150 # The default value for build_file_generation is "auto" if no override is found, but will default to "on" if an override is found. 151 return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_BUILD_FILE_GENERATION_BY_PATH, path, "auto", "build_file_generation") 152 153 def _get_build_extra_args(path, gazelle_overrides, gazelle_default_attributes): 154 return _get_override_or_default(gazelle_overrides, gazelle_default_attributes, DEFAULT_BUILD_EXTRA_ARGS_BY_PATH, path, [], "build_extra_args") 155 156 def _get_patches(path, module_overrides): 157 return _get_override_or_default(module_overrides, struct(), {}, path, [], "patches") 158 159 def _get_patch_args(path, module_overrides): 160 override = _get_override_or_default(module_overrides, struct(), {}, path, None, "patch_strip") 161 return ["-p{}".format(override)] if override else [] 162 163 def _repo_name(importpath): 164 path_segments = importpath.split("/") 165 segments = reversed(path_segments[0].split(".")) + path_segments[1:] 166 candidate_name = "_".join(segments).replace("-", "_") 167 return "".join([c.lower() if c.isalnum() else "_" for c in candidate_name.elems()]) 168 169 def _is_dev_dependency(module_ctx, tag): 170 if hasattr(tag, "_is_dev_dependency"): 171 # Synthetic tags generated from go_deps.from_file have this "hidden" attribute. 172 return tag._is_dev_dependency 173 174 # This function is available in Bazel 6.2.0 and later. This is the same version that has 175 # module_ctx.extension_metadata, so the return value of this function is not used if it is 176 # not available. 177 return module_ctx.is_dev_dependency(tag) if hasattr(module_ctx, "is_dev_dependency") else False 178 179 def _intersperse_newlines(tags): 180 return [tag for p in zip(tags, len(tags) * ["\n"]) for tag in p] 181 182 # This function processes the gazelle_default_attributes tag for a given module and returns a struct 183 # containing the attributes from _GAZELLE_ATTRS that are defined in the tag. 184 def _process_gazelle_default_attributes(module_ctx): 185 for module in module_ctx.modules: 186 _fail_on_non_root_overrides(module_ctx, module, "gazelle_default_attributes") 187 188 for module in module_ctx.modules: 189 tags = module.tags.gazelle_default_attributes 190 if not tags: 191 continue 192 193 if len(tags) > 1: 194 fail( 195 "go_deps.gazelle_default_attributes: only one tag can be specified per module, got:\n", 196 *[t for p in zip(module.tags.gazelle_default_attributes, len(module.tags.gazelle_default_attributes) * ["\n"]) for t in p] 197 ) 198 199 tag = tags[0] 200 return struct(**{ 201 attr: getattr(tag, attr) 202 for attr in _GAZELLE_ATTRS.keys() 203 if hasattr(tag, attr) 204 }) 205 206 return None 207 208 # This function processes a given override type for a given module, checks for duplicate overrides 209 # and inserts the override returned from the process_override_func into the overrides dict. 210 def _process_overrides(module_ctx, module, override_type, overrides, process_override_func, additional_overrides = None): 211 _fail_on_non_root_overrides(module_ctx, module, override_type) 212 for override_tag in getattr(module.tags, override_type): 213 _fail_on_duplicate_overrides(override_tag.path, module.name, overrides) 214 215 # Some overrides conflict with other overrides. These can be specified in the 216 # additional_overrides dict. If the override is in the additional_overrides dict, then fail. 217 if additional_overrides: 218 _fail_on_duplicate_overrides(override_tag.path, module.name, additional_overrides) 219 220 overrides[override_tag.path] = process_override_func(override_tag) 221 222 def _process_gazelle_override(gazelle_override_tag): 223 for directive in gazelle_override_tag.directives: 224 _check_directive(directive) 225 226 return struct(**{ 227 attr: getattr(gazelle_override_tag, attr) 228 for attr in _GAZELLE_ATTRS.keys() 229 if hasattr(gazelle_override_tag, attr) 230 }) 231 232 def _process_module_override(module_override_tag): 233 return struct( 234 patches = module_override_tag.patches, 235 patch_strip = module_override_tag.patch_strip, 236 ) 237 238 def _process_archive_override(archive_override_tag): 239 return struct( 240 urls = archive_override_tag.urls, 241 sha256 = archive_override_tag.sha256, 242 strip_prefix = archive_override_tag.strip_prefix, 243 patches = archive_override_tag.patches, 244 patch_strip = archive_override_tag.patch_strip, 245 ) 246 247 def _go_repository_config_impl(ctx): 248 repos = [] 249 for name, importpath in sorted(ctx.attr.importpaths.items()): 250 repos.append(format_rule_call( 251 "go_repository", 252 name = name, 253 importpath = importpath, 254 module_name = ctx.attr.module_names.get(name), 255 build_naming_convention = ctx.attr.build_naming_conventions.get(name), 256 )) 257 258 ctx.file("WORKSPACE", "\n".join(repos)) 259 ctx.file("BUILD.bazel", "exports_files(['WORKSPACE', 'config.json'])") 260 ctx.file("go_env.bzl", content = "GO_ENV = " + repr(ctx.attr.go_env)) 261 262 # For use by @rules_go//go. 263 ctx.file("config.json", content = json.encode_indent({ 264 "go_env": ctx.attr.go_env, 265 "dep_files": ctx.attr.dep_files, 266 })) 267 268 _go_repository_config = repository_rule( 269 implementation = _go_repository_config_impl, 270 attrs = { 271 "importpaths": attr.string_dict(mandatory = True), 272 "module_names": attr.string_dict(mandatory = True), 273 "build_naming_conventions": attr.string_dict(mandatory = True), 274 "go_env": attr.string_dict(mandatory = True), 275 "dep_files": attr.string_list(), 276 }, 277 ) 278 279 def check_for_version_conflict(version, previous, module_tag, module_name_to_go_dot_mod_label, conflict_printer): 280 """ 281 Check if duplicate modules have different versions, and fail with a useful error message if they do. 282 283 Args: 284 version: The version of the module. 285 previous: The previous module object. 286 module_tag: The module tag. 287 module_name_to_go_dot_mod_label: A dictionary mapping module paths to go.mod labels. 288 conflict_printer: a printer function to use for printing the error message, generally either print or fail. 289 """ 290 291 if not previous or version == previous.version: 292 # no previous module, so no possible error OR 293 # version is the same, skip because we won't error 294 return 295 296 if hasattr(module_tag, "local_path"): 297 # overrides are not considered for version conflicts 298 return 299 300 # When using go.work, duplicate dependency versions are possible. 301 # This can cause issues, so we fail with a hopefully actionable error. 302 current_label = module_tag._parent_label 303 304 previous_label = previous.module_tag._parent_label 305 306 corrective_measure = """To correct this: 307 1. ensure that '{}' in all go.mod files is the same version. 308 2. in the folders where you made changes run: bazel run @rules_go//go -- mod tidy 309 3. at the workspace root run: bazel run @rules_go//go -- work sync.""".format(module_tag.path) 310 311 message = """Multiple versions of {} found: 312 - {} contains: {} 313 - {} contains: {} 314 {}""".format(module_tag.path, current_label, module_tag.version, previous_label, previous.module_tag.version, corrective_measure) 315 316 conflict_printer(message) 317 318 def _noop(_): 319 pass 320 321 # These repos are shared between the isolated and non-isolated instances of go_deps as they are 322 # referenced directly by rules (go_proto_library) and would result in linker errors due to duplicate 323 # packages if they were resolved separately. 324 # When adding a new Go module to this list, make sure that: 325 # 1. The corresponding repository is visible to the gazelle module via a use_repo directive. 326 # 2. All transitive dependencies of the module are also in this list. Avoid adding module that have 327 # a large number of transitive dependencies. 328 _SHARED_REPOS = [ 329 "github.com/golang/protobuf", 330 "google.golang.org/protobuf", 331 ] 332 333 def _go_deps_impl(module_ctx): 334 module_resolutions = {} 335 sums = {} 336 replace_map = {} 337 bazel_deps = {} 338 339 gazelle_default_attributes = _process_gazelle_default_attributes(module_ctx) 340 archive_overrides = {} 341 gazelle_overrides = {} 342 module_overrides = {} 343 344 root_versions = {} 345 root_module_direct_deps = {} 346 root_module_direct_dev_deps = {} 347 348 first_module = module_ctx.modules[0] 349 if first_module.is_root and first_module.name in ["gazelle", "rules_go"]: 350 root_module_direct_deps["bazel_gazelle_go_repository_config"] = None 351 352 outdated_direct_dep_printer = print 353 go_env = {} 354 dep_files = [] 355 debug_mode = False 356 for module in module_ctx.modules: 357 if len(module.tags.config) > 1: 358 fail( 359 "Multiple \"go_deps.config\" tags defined in module \"{}\":\n".format(module.name), 360 *_intersperse_newlines(module.tags.config) 361 ) 362 363 # Parse the go_deps.config tag of the root module only. 364 for mod_config in module.tags.config: 365 if not module.is_root: 366 continue 367 check_direct_deps = mod_config.check_direct_dependencies 368 if check_direct_deps == "off": 369 outdated_direct_dep_printer = _noop 370 elif check_direct_deps == "warning": 371 outdated_direct_dep_printer = print 372 elif check_direct_deps == "error": 373 outdated_direct_dep_printer = fail 374 go_env = mod_config.go_env 375 debug_mode = mod_config.debug_mode 376 377 _process_overrides(module_ctx, module, "gazelle_override", gazelle_overrides, _process_gazelle_override) 378 _process_overrides(module_ctx, module, "module_override", module_overrides, _process_module_override, archive_overrides) 379 _process_overrides(module_ctx, module, "archive_override", archive_overrides, _process_archive_override, module_overrides) 380 381 if len(module.tags.from_file) > 1: 382 fail( 383 "Multiple \"go_deps.from_file\" tags defined in module \"{}\": {}".format( 384 module.name, 385 ", ".join([str(tag.go_mod) for tag in module.tags.from_file]), 386 ), 387 ) 388 389 additional_module_tags = [] 390 from_file_tags = [] 391 module_name_to_go_dot_mod_label = {} 392 393 for from_file_tag in module.tags.from_file: 394 if bool(from_file_tag.go_work) == bool(from_file_tag.go_mod): 395 fail("go_deps.from_file tag must have either go_work or go_mod attribute, but not both.") 396 397 if from_file_tag.go_mod: 398 from_file_tags.append(from_file_tag) 399 elif from_file_tag.go_work: 400 if module.is_root != True: 401 fail("go_deps.from_file(go_work = '{}') tag can only be used from a root module but: '{}' is not a root module.".format(from_file_tag.go_work, module.name)) 402 403 go_work = go_work_from_label(module_ctx, from_file_tag.go_work) 404 405 # this ensures go.work replacements are considered 406 additional_module_tags += [ 407 with_replaced_or_new_fields(tag, _is_dev_dependency = False) 408 for tag in go_work.module_tags 409 ] 410 411 for entry, new_sum in sums_from_go_work(module_ctx, from_file_tag.go_work).items(): 412 _safe_insert_sum(sums, entry, new_sum) 413 414 replace_map.update(go_work.replace_map) 415 from_file_tags = from_file_tags + go_work.from_file_tags 416 else: 417 fail("Either \"go_mod\" or \"go_work\" must be specified in \"go_deps.from_file\" tags.") 418 419 for from_file_tag in from_file_tags: 420 module_path, module_tags_from_go_mod, go_mod_replace_map, module_name = deps_from_go_mod(module_ctx, from_file_tag.go_mod) 421 module_name_to_go_dot_mod_label[module_name] = from_file_tag.go_mod 422 423 # Collect the relative path of the root module's go.mod file if it lives in the main 424 # repository. 425 if module.is_root and not from_file_tag.go_mod.workspace_name: 426 go_mod = "go.mod" 427 if from_file_tag.go_mod.package: 428 go_mod = from_file_tag.go_mod.package + "/" + go_mod 429 dep_files.append(go_mod) 430 431 is_dev_dependency = _is_dev_dependency(module_ctx, from_file_tag) 432 additional_module_tags += [ 433 with_replaced_or_new_fields(tag, _is_dev_dependency = is_dev_dependency) 434 for tag in module_tags_from_go_mod 435 ] 436 437 if module.is_root or getattr(module_ctx, "is_isolated", False): 438 # for the replace_map, first in wins 439 for mod_path, mod in go_mod_replace_map.items(): 440 if not mod_path in replace_map: 441 replace_map[mod_path] = mod 442 else: 443 # Register this Bazel module as providing the specified Go module. It participates 444 # in version resolution using its registry version, which uses a relaxed variant of 445 # semver that can however still be compared to strict semvers. 446 # An empty version string signals an override, which is assumed to be newer than any 447 # other version. 448 raw_version = _canonicalize_raw_version(module.version) 449 version = semver.to_comparable(raw_version, relaxed = True) if raw_version else _HIGHEST_VERSION_SENTINEL 450 if module_path not in bazel_deps or version > bazel_deps[module_path].version: 451 bazel_deps[module_path] = struct( 452 module_name = module.name, 453 repo_name = "@" + from_file_tag.go_mod.workspace_name, 454 version = version, 455 raw_version = raw_version, 456 ) 457 458 # Load all sums from transitively resolved `go.sum` files that have modules. 459 if len(module_tags_from_go_mod) > 0: 460 for entry, new_sum in sums_from_go_mod(module_ctx, from_file_tag.go_mod).items(): 461 _safe_insert_sum(sums, entry, new_sum) 462 463 # Load sums from manually specified modules separately. 464 for module_tag in module.tags.module: 465 if module_tag.build_naming_convention: 466 fail("""The "build_naming_convention" attribute is no longer supported for "go_deps.module" tags. Use a "gazelle:go_naming_convention" directive via the "gazelle_override" tag's "directives" attribute instead.""") 467 if module_tag.build_file_proto_mode: 468 fail("""The "build_file_proto_mode" attribute is no longer supported for "go_deps.module" tags. Use a "gazelle:proto" directive via the "gazelle_override" tag's "directives" attribute instead.""") 469 sum_version = _canonicalize_raw_version(module_tag.version) 470 _safe_insert_sum(sums, (module_tag.path, sum_version), module_tag.sum) 471 472 # Parse the go_dep.module tags of all transitive dependencies and apply 473 # Minimum Version Selection to resolve importpaths to Go module versions 474 # and sums. 475 # 476 # Note: This applies Minimum Version Selection on the resolved 477 # dependency graphs of all transitive Bazel module dependencies, which 478 # is not what `go mod` does. But since this algorithm ends up using only 479 # Go module versions that have been explicitly declared somewhere in the 480 # full graph, we can assume that at that place all its required 481 # transitive dependencies have also been declared - we may end up 482 # resolving them to higher versions, but only compatible ones. 483 paths = {} 484 485 for module_tag in module.tags.module + additional_module_tags: 486 if module_tag.path in bazel_deps: 487 continue 488 489 raw_version = _canonicalize_raw_version(module_tag.version) 490 491 # For modules imported from a go.sum, we know which ones are direct 492 # dependencies and can thus only report implicit version upgrades 493 # for direct dependencies. For manually specified go_deps.module 494 # tags, we always report version upgrades unless users override with 495 # the "indirect" attribute. 496 if module.is_root and not module_tag.indirect: 497 root_versions[module_tag.path] = raw_version 498 if _is_dev_dependency(module_ctx, module_tag): 499 root_module_direct_dev_deps[_repo_name(module_tag.path)] = None 500 else: 501 root_module_direct_deps[_repo_name(module_tag.path)] = None 502 503 version = semver.to_comparable(raw_version) 504 previous = paths.get(module_tag.path) 505 506 fail_on_version_conflict = any([x.fail_on_version_conflict for x in module.tags.from_file]) 507 508 conflict_printer = fail if fail_on_version_conflict else print 509 check_for_version_conflict(version, previous, module_tag, module_name_to_go_dot_mod_label, conflict_printer) 510 paths[module_tag.path] = struct(version = version, module_tag = module_tag) 511 512 if module_tag.path not in module_resolutions or version > module_resolutions[module_tag.path].version: 513 to_path = None 514 local_path = None 515 516 if module_tag.path in replace_map: 517 replacement = replace_map[module_tag.path] 518 519 to_path = replacement.to_path 520 local_path = replacement.local_path 521 522 module_resolutions[module_tag.path] = struct( 523 repo_name = _repo_name(module_tag.path), 524 version = version, 525 raw_version = raw_version, 526 to_path = to_path, 527 local_path = local_path, 528 ) 529 530 _fail_on_unmatched_overrides(archive_overrides.keys(), module_resolutions, "archive_overrides") 531 _fail_on_unmatched_overrides(gazelle_overrides.keys(), module_resolutions, "gazelle_overrides") 532 _fail_on_unmatched_overrides(module_overrides.keys(), module_resolutions, "module_overrides") 533 534 # All `replace` directives are applied after version resolution. 535 # We can simply do this by checking the replace paths' existence 536 # in the module resolutions and swapping out the entry. 537 for path, replace in replace_map.items(): 538 if path in module_resolutions: 539 # If the replace directive specified a version then we only 540 # apply it if the versions match. 541 if replace.from_version: 542 comparable_from_version = semver.to_comparable(replace.from_version) 543 if module_resolutions[path].version != comparable_from_version: 544 continue 545 546 new_version = semver.to_comparable(replace.version) 547 module_resolutions[path] = with_replaced_or_new_fields( 548 module_resolutions[path], 549 replace = replace.to_path, 550 version = new_version, 551 raw_version = replace.version, 552 ) 553 if path in root_versions: 554 if replace != replace.to_path: 555 # If the root module replaces a Go module with a completely different one, do 556 # not ever report an implicit version upgrade. 557 root_versions.pop(path) 558 else: 559 root_versions[path] = replace.version 560 561 for path, bazel_dep in bazel_deps.items(): 562 # We can't apply overrides to Bazel dependencies and thus fall back to using the Go module. 563 if path in archive_overrides or path in gazelle_overrides or path in module_overrides or path in replace_map: 564 continue 565 566 # Only use the Bazel module if it is at least as high as the required Go module version. 567 if path in module_resolutions and bazel_dep.version < module_resolutions[path].version: 568 outdated_direct_dep_printer( 569 "Go module \"{path}\" is provided by Bazel module \"{bazel_module}\" in version {bazel_dep_version}, but requested at higher version {go_version} via Go requirements. Consider adding or updating an appropriate \"bazel_dep\" to ensure that the Bazel module is used to provide the Go module.".format( 570 path = path, 571 bazel_module = bazel_dep.module_name, 572 bazel_dep_version = bazel_dep.raw_version, 573 go_version = module_resolutions[path].raw_version, 574 ), 575 ) 576 continue 577 578 # TODO: We should update root_versions if the bazel_dep is a direct dependency of the root 579 # module. However, we currently don't have a way to determine that. 580 module_resolutions[path] = bazel_dep 581 582 for path, root_version in root_versions.items(): 583 if semver.to_comparable(root_version) < module_resolutions[path].version: 584 outdated_direct_dep_printer( 585 "For Go module \"{path}\", the root module requires module version v{root_version}, but got v{resolved_version} in the resolved dependency graph.".format( 586 path = path, 587 root_version = root_version, 588 resolved_version = module_resolutions[path].raw_version, 589 ), 590 ) 591 592 for path, module in module_resolutions.items(): 593 if hasattr(module, "module_name"): 594 # Do not create a go_repository for a Go module provided by a bazel_dep. 595 root_module_direct_deps.pop(_repo_name(path), default = None) 596 root_module_direct_dev_deps.pop(_repo_name(path), default = None) 597 continue 598 if getattr(module_ctx, "is_isolated", False) and path in _SHARED_REPOS: 599 # Do not create a go_repository for a dep shared with the non-isolated instance of 600 # go_deps. 601 continue 602 go_repository_args = { 603 "name": module.repo_name, 604 "importpath": path, 605 "build_directives": _get_directives(path, gazelle_overrides, gazelle_default_attributes), 606 "build_file_generation": _get_build_file_generation(path, gazelle_overrides, gazelle_default_attributes), 607 "build_extra_args": _get_build_extra_args(path, gazelle_overrides, gazelle_default_attributes), 608 "patches": _get_patches(path, module_overrides), 609 "patch_args": _get_patch_args(path, module_overrides), 610 "debug_mode": debug_mode, 611 } 612 613 archive_override = archive_overrides.get(path) 614 if archive_override: 615 go_repository_args.update({ 616 "urls": archive_override.urls, 617 "strip_prefix": archive_override.strip_prefix, 618 "sha256": archive_override.sha256, 619 "patches": _get_patches(path, archive_overrides), 620 "patch_args": _get_patch_args(path, archive_overrides), 621 }) 622 elif module.local_path: 623 go_repository_args.update({ 624 # the version is now meaningless 625 "version": None, 626 "local_path": module.local_path, 627 }) 628 else: 629 go_repository_args.update({ 630 "sum": _get_sum_from_module(path, module, sums), 631 "replace": getattr(module, "replace", None), 632 "version": "v" + module.raw_version, 633 }) 634 635 go_repository(**go_repository_args) 636 637 # Create a synthetic WORKSPACE file that lists all Go repositories created 638 # above and contains all the information required by Gazelle's -repo_config 639 # to generate BUILD files for external Go modules. This skips the need to 640 # run generate_repo_config. Only "importpath" and "build_naming_convention" 641 # are relevant. 642 _go_repository_config( 643 name = "bazel_gazelle_go_repository_config", 644 importpaths = { 645 module.repo_name: path 646 for path, module in module_resolutions.items() 647 }, 648 module_names = { 649 info.repo_name: info.module_name 650 for path, info in bazel_deps.items() 651 }, 652 build_naming_conventions = drop_nones({ 653 module.repo_name: get_directive_value( 654 _get_directives(path, gazelle_overrides, gazelle_default_attributes), 655 "go_naming_convention", 656 ) 657 for path, module in module_resolutions.items() 658 }), 659 go_env = go_env, 660 dep_files = dep_files, 661 ) 662 663 return extension_metadata( 664 module_ctx, 665 root_module_direct_deps = root_module_direct_deps.keys(), 666 # If a Go module appears as both a dev and a non-dev dependency, it has to be imported as a 667 # non-dev dependency. 668 root_module_direct_dev_deps = { 669 repo_name: None 670 for repo_name in root_module_direct_dev_deps.keys() 671 if repo_name not in root_module_direct_deps 672 }.keys(), 673 reproducible = True, 674 ) 675 676 def _get_sum_from_module(path, module, sums): 677 entry = (path, module.raw_version) 678 if hasattr(module, "replace"): 679 entry = (module.replace, module.raw_version) 680 681 if entry not in sums: 682 if module.raw_version == COMPARES_HIGHEST_SENTINEL: 683 # replacement have no sums, so we can skip this 684 return None 685 elif module.local_path == None: 686 fail("No sum for {}@{} from {} found. You may need to run: bazel run @rules_go//go -- mod tidy".format(path, module.raw_version, "parent-label-todo")) #module.parent_label)) 687 688 return sums[entry] 689 690 def _safe_insert_sum(sums, entry, new_sum): 691 if entry in sums and new_sum != sums[entry]: 692 fail("Multiple mismatching sums for {}@{} found: {} vs {}".format(entry[0], entry[1], new_sum, sums[entry])) 693 sums[entry] = new_sum 694 695 def _canonicalize_raw_version(raw_version): 696 if raw_version.startswith("v"): 697 return raw_version[1:] 698 return raw_version 699 700 _config_tag = tag_class( 701 attrs = { 702 "check_direct_dependencies": attr.string( 703 values = ["off", "warning", "error"], 704 ), 705 "go_env": attr.string_dict( 706 doc = "The environment variables to use when fetching Go dependencies or running the `@rules_go//go` tool.", 707 ), 708 "debug_mode": attr.bool(doc = "Whether or not to print stdout and stderr messages from gazelle", default = False), 709 }, 710 ) 711 712 _from_file_tag = tag_class( 713 attrs = { 714 "go_mod": attr.label(mandatory = False), 715 "go_work": attr.label(mandatory = False), 716 "fail_on_version_conflict": attr.bool( 717 default = True, 718 doc = "Fail if duplicate modules have different versions", 719 ), 720 }, 721 ) 722 723 _module_tag = tag_class( 724 attrs = { 725 "path": attr.string(mandatory = True), 726 "version": attr.string(mandatory = True), 727 "sum": attr.string(), 728 "indirect": attr.bool( 729 doc = """Whether this Go module is an indirect dependency.""", 730 default = False, 731 ), 732 "build_naming_convention": attr.string(doc = """Removed, do not use""", default = ""), 733 "build_file_proto_mode": attr.string(doc = """Removed, do not use""", default = ""), 734 "local_path": attr.string( 735 doc = """For when a module is replaced by one residing in a local directory path """, 736 mandatory = False, 737 ), 738 }, 739 ) 740 741 _archive_override_tag = tag_class( 742 attrs = { 743 "path": attr.string( 744 doc = """The Go module path for the repository to be overridden. 745 746 This module path must be defined by other tags in this 747 extension within this Bazel module.""", 748 mandatory = True, 749 ), 750 "urls": attr.string_list( 751 doc = """A list of HTTP(S) URLs where an archive containing the project can be 752 downloaded. Bazel will attempt to download from the first URL; the others 753 are mirrors.""", 754 ), 755 "strip_prefix": attr.string( 756 doc = """If the repository is downloaded via HTTP (`urls` is set), this is a 757 directory prefix to strip. See [`http_archive.strip_prefix`].""", 758 ), 759 "sha256": attr.string( 760 doc = """If the repository is downloaded via HTTP (`urls` is set), this is the 761 SHA-256 sum of the downloaded archive. When set, Bazel will verify the archive 762 against this sum before extracting it.""", 763 ), 764 "patches": attr.label_list( 765 doc = "A list of patches to apply to the repository *after* gazelle runs.", 766 ), 767 "patch_strip": attr.int( 768 default = 0, 769 doc = "The number of leading path segments to be stripped from the file name in the patches.", 770 ), 771 }, 772 doc = "Override the default source location on a given Go module in this extension.", 773 ) 774 775 _gazelle_override_tag = tag_class( 776 attrs = { 777 "path": attr.string( 778 doc = """The Go module path for the repository to be overridden. 779 780 This module path must be defined by other tags in this 781 extension within this Bazel module.""", 782 mandatory = True, 783 ), 784 } | _GAZELLE_ATTRS, 785 doc = "Override Gazelle's behavior on a given Go module defined by other tags in this extension.", 786 ) 787 788 _gazelle_default_attributes_tag = tag_class( 789 attrs = _GAZELLE_ATTRS, 790 doc = "Override Gazelle's default attribute values for all modules in this extension.", 791 ) 792 793 _module_override_tag = tag_class( 794 attrs = { 795 "path": attr.string( 796 doc = """The Go module path for the repository to be overridden. 797 798 This module path must be defined by other tags in this 799 extension within this Bazel module.""", 800 mandatory = True, 801 ), 802 "patches": attr.label_list( 803 doc = "A list of patches to apply to the repository *after* gazelle runs.", 804 ), 805 "patch_strip": attr.int( 806 default = 0, 807 doc = "The number of leading path segments to be stripped from the file name in the patches.", 808 ), 809 }, 810 doc = "Apply patches to a given Go module defined by other tags in this extension.", 811 ) 812 813 go_deps = module_extension( 814 _go_deps_impl, 815 tag_classes = { 816 "archive_override": _archive_override_tag, 817 "config": _config_tag, 818 "from_file": _from_file_tag, 819 "gazelle_override": _gazelle_override_tag, 820 "gazelle_default_attributes": _gazelle_default_attributes_tag, 821 "module": _module_tag, 822 "module_override": _module_override_tag, 823 }, 824 )