diff --git a/MODULE.bazel b/MODULE.bazel index ce36c8de6..7a099697f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -8,6 +8,7 @@ bazel_dep(name = "bazel_features", version = "1.9.1") bazel_dep(name = "bazel_skylib", version = "1.5.0") bazel_dep(name = "protobuf", version = "3.19.6", repo_name = "com_google_protobuf") bazel_dep(name = "rules_go", version = "0.47.0", repo_name = "io_bazel_rules_go") +bazel_dep(name = "rules_license", version = "0.0.8") bazel_dep(name = "rules_proto", version = "4.0.0") go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk") diff --git a/deps.bzl b/deps.bzl index 84d48aba0..5e9da6eb3 100644 --- a/deps.bzl +++ b/deps.bzl @@ -54,6 +54,16 @@ def gazelle_dependencies( sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506", ) + _maybe( + http_archive, + name = "rules_license", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_license/releases/download/0.0.8/rules_license-0.0.8.tar.gz", + "https://github.com/bazelbuild/rules_license/releases/download/0.0.8/rules_license-0.0.8.tar.gz", + ], + sha256 = "241b06f3097fd186ff468832150d6cc142247dc42a32aaefb56d0099895fd229", + ) + if go_sdk: go_repository_cache( name = "bazel_gazelle_go_repository_cache", diff --git a/internal/go_repository.bzl b/internal/go_repository.bzl index 8e8027d45..01b66d573 100644 --- a/internal/go_repository.bzl +++ b/internal/go_repository.bzl @@ -361,6 +361,50 @@ def _go_repository_impl(ctx): # Apply patches if necessary. patch(ctx) + if generate: + # Do not override a REPO.bazel patched in by users. This also provides a + # way for users to opt out of Gazelle-generated package_info. + repo_file = ctx.path("REPO.bazel") + if not repo_file.exists: + ctx.file("REPO.bazel", """\ +repo(default_package_metadata = ["//:gazelle_generated_package_info"]) +""") + + # Modify the top-level build file after patches have been applied as the + # patches may otherwise conflict with our generated content. + build_file = ctx.path(build_file_name) + if build_file.exists: + build_file_content = ctx.read(build_file) + else: + build_file_content = "" + build_file_content += _generate_package_info( + importpath = ctx.attr.importpath, + version = ctx.attr.version, + ) + ctx.file(build_file_name, build_file_content) + +def _generate_package_info(*, importpath, version): + package_name = importpath + + # TODO: Consider adding support for custom remotes. + package_url = "https://" + importpath if version else None + package_version = version.removeprefix("v") if version else None + return """ +load("@rules_license//rules:package_info.bzl", "package_info") + +package_info( + name = "gazelle_generated_package_info", + package_name = {package_name}, + package_url = {package_url}, + package_version = {package_version}, + visibility = ["//:__subpackages__"], +) +""".format( + package_name = repr(package_name), + package_url = repr(package_url), + package_version = repr(package_version), + ) + go_repository = repository_rule( implementation = _go_repository_impl, doc = _DOC, diff --git a/tests/bcr/go_mod/BUILD.bazel b/tests/bcr/go_mod/BUILD.bazel index 81f451bd4..effec7a60 100644 --- a/tests/bcr/go_mod/BUILD.bazel +++ b/tests/bcr/go_mod/BUILD.bazel @@ -1,4 +1,5 @@ load("@gazelle//:def.bzl", "gazelle", "gazelle_test") +load(":tests.bzl", "starlark_tests") # gazelle:go_naming_convention import # gazelle:go_naming_convention_external import @@ -8,3 +9,7 @@ gazelle_test( name = "gazelle_test", workspace = "//:BUILD.bazel", ) + +starlark_tests( + name = "starlark_tests", +) diff --git a/tests/bcr/go_mod/MODULE.bazel b/tests/bcr/go_mod/MODULE.bazel index c8c52f0f9..bd2e0d083 100644 --- a/tests/bcr/go_mod/MODULE.bazel +++ b/tests/bcr/go_mod/MODULE.bazel @@ -14,9 +14,12 @@ local_path_override( path = "test_dep", ) +bazel_dep(name = "bazel_features", version = "1.14.0") bazel_dep(name = "protobuf", version = "23.1", repo_name = "my_protobuf") bazel_dep(name = "rules_go", version = "0.42.0", repo_name = "my_rules_go") +bazel_dep(name = "rules_license", version = "0.0.8") bazel_dep(name = "rules_proto", version = "6.0.0-rc2", repo_name = "my_rules_proto") +bazel_dep(name = "rules_testing", version = "0.6.0") go_sdk = use_extension("@my_rules_go//go:extensions.bzl", "go_sdk") diff --git a/tests/bcr/go_mod/tests.bzl b/tests/bcr/go_mod/tests.bzl new file mode 100644 index 000000000..ba5d75da8 --- /dev/null +++ b/tests/bcr/go_mod/tests.bzl @@ -0,0 +1,64 @@ +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_license//rules:providers.bzl", "PackageInfo") +load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite") +load("@rules_testing//lib:truth.bzl", "subjects") + +def _test_package_info(name): + analysis_test( + name = name, + impl = _test_package_info_impl, + target = "@com_github_fmeum_dep_on_gazelle//:go_default_library", + extra_target_under_test_aspects = [ + _package_info_aspect, + ], + provider_subject_factories = [_PackageInfoSubjectFactory], + ) + +def _test_package_info_impl(env, target): + # The package_info functionality requires REPO.bazel support, which is only + # available in Bazel 7 and higher. Use this unrelated feature launched in + # Bazel 7 as a hacky signal to skip the test if the feature is not + # available. + if not bazel_features.proto.starlark_proto_info: + return + env.expect.that_target(target).has_provider(PackageInfo) + subject = env.expect.that_target(target).provider(PackageInfo) + subject.package_name().equals("github.com/fmeum/dep_on_gazelle") + subject.package_version().equals("1.0.0") + subject.package_url().equals("https://github.com/fmeum/dep_on_gazelle") + +def _package_info_aspect_impl(_, ctx): + if hasattr(ctx.rule.attr, "applicable_licenses"): + attr = ctx.rule.attr.applicable_licenses + elif hasattr(ctx.rule.attr, "package_metadata"): + attr = ctx.rule.attr.package_metadata + if attr and PackageInfo in attr[0]: + return [attr[0][PackageInfo]] + return [] + +_package_info_aspect = aspect( + implementation = _package_info_aspect_impl, + doc = "Forwards metadata annotations on the target via the PackageInfo provider.", +) + +_PackageInfoSubjectFactory = struct( + type = PackageInfo, + name = "PackageInfo", + factory = lambda actual, *, meta: subjects.struct( + actual, + meta = meta, + attrs = { + "package_name": subjects.str, + "package_version": subjects.str, + "package_url": subjects.str, + }, + ), +) + +def starlark_tests(name): + test_suite( + name = name, + tests = [ + _test_package_info, + ], + )