blob: f391ab5c86c632395830597e0f0aacaad3cb3a26 [file]
load("@bazel_features//:features.bzl", "bazel_features")
load("//private/lib:coordinates.bzl", "unpack_coordinates")
load(":maven_bom_fragment.bzl", "MavenBomFragmentInfo")
load(":maven_publish.bzl", "maven_publish")
load(":maven_utils.bzl", "generate_pom")
def _label(label_or_string):
if type(label_or_string) == "Label":
return label_or_string
workspace_prefix = "@@" if bazel_features.external_deps.is_bzlmod_enabled else "@"
if type(label_or_string) == "string":
# We may have a target of the form: `@bar//foo`, `//foo`, `//foo:foo`, `:foo`, `foo`
if label_or_string.startswith("@"):
# We have an absolute target. Great!
return Label(label_or_string)
elif label_or_string.startswith("//"):
return Label("%s%s" % (workspace_prefix, label_or_string))
else:
if label_or_string.startswith(":"):
label_or_string = label_or_string[1:]
return Label("%s//%s:%s" % (workspace_prefix, native.package_name(), label_or_string))
fail("Can only convert either labels or strings: %s" % label_or_string)
def _maven_bom_impl(ctx):
fragments = [f[MavenBomFragmentInfo] for f in ctx.attr.fragments]
# Expand maven coordinates for any variables to be replaced.
coordinates = ctx.expand_make_variables("coordinates", ctx.attr.maven_coordinates, {})
bom = generate_pom(
ctx,
coordinates = coordinates,
versioned_dep_coordinates = [f[MavenBomFragmentInfo].coordinates for f in ctx.attr.fragments],
pom_template = ctx.file.pom_template,
out_name = "%s.xml" % ctx.label.name,
)
return [
DefaultInfo(files = depset([bom])),
]
_maven_bom = rule(
_maven_bom_impl,
doc = """Create a Maven BOM file (`pom.xml`) for the given targets.""",
attrs = {
"maven_coordinates": attr.string(
mandatory = True,
),
"pom_template": attr.label(
doc = "Template file to use for the BOM pom.xml",
default = "//private/templates:bom.tpl",
allow_single_file = True,
),
"fragments": attr.label_list(
providers = [
[MavenBomFragmentInfo],
],
),
},
)
def _maven_dependencies_bom_impl(ctx):
fragments = [f[MavenBomFragmentInfo] for f in ctx.attr.fragments]
# We want to include all the dependencies that aren't
# included in the main BOM
first_order_deps = [f[MavenBomFragmentInfo].coordinates for f in ctx.attr.fragments]
all_deps = depset(transitive = [f.maven_info.maven_deps for f in fragments]).to_list()
combined_deps = [a for a in all_deps if a not in first_order_deps]
unpacked = unpack_coordinates(ctx.attr.bom_coordinates)
dependencies_bom = generate_pom(
ctx,
coordinates = ctx.attr.maven_coordinates,
versioned_dep_coordinates = combined_deps + ["%s:%s:pom:import:%s" % (unpacked.groupId, unpacked.artifactId, unpacked.version)],
pom_template = ctx.file.pom_template,
out_name = "%s.xml" % ctx.label.name,
indent = 12,
)
return [
DefaultInfo(files = depset([dependencies_bom])),
]
_maven_dependencies_bom = rule(
_maven_dependencies_bom_impl,
doc = """Create a Maven dependencies `pom.xml` for the given targets.""",
attrs = {
"maven_coordinates": attr.string(
mandatory = True,
),
"pom_template": attr.label(
doc = "Template file to use for the pom.xml",
default = "//private/templates:dependencies-bom.tpl",
allow_single_file = True,
),
"fragments": attr.label_list(
providers = [
[MavenBomFragmentInfo],
],
),
"bom_coordinates": attr.string(
doc = "Coordinates of the bom to include",
mandatory = True,
),
},
)
def maven_bom(
name,
maven_coordinates,
java_exports,
bom_pom_template = None,
dependencies_maven_coordinates = None,
dependencies_pom_template = None,
tags = None,
testonly = None,
visibility = None):
"""Generates a Maven BOM `pom.xml` file and an optional "dependencies" `pom.xml`.
The generated BOM will contain a list of all the coordinates of the
`java_export` targets in the `java_exports` parameters. An optional
dependencies artifact will be created if the parameter
`dependencies_maven_coordinates` is set.
Both the BOM and dependencies artifact can be templatised to support
customisation, but a sensible default template will be used if none is
provided. The template used is derived from the (optional)
`pom_template` argument, and the following substitutions are performed on
the template file:
* `{groupId}`: Replaced with the maven coordinates group ID.
* `{artifactId}`: Replaced with the maven coordinates artifact ID.
* `{version}`: Replaced by the maven coordinates version.
* `{dependencies}`: Replaced by a list of maven dependencies directly relied upon
by java_library targets within the artifact.
To publish, call the implicit `*.publish` target(s).
The maven repository may accessed locally using a `file://` URL, or
remotely using an `https://` URL. The following flags may be set
using `--define`:
* `gpg_sign`: Whether to sign artifacts using GPG
* `maven_repo`: A URL for the repo to use. May be "https" or "file".
* `maven_user`: The user name to use when uploading to the maven repository.
* `maven_password`: The password to use when uploading to the maven repository.
When signing with GPG, the current default key is used.
Generated rules:
* `name`: The BOM file itself.
* `name.publish`: To be executed by `bazel run` to publish the BOM to a maven repo
* `name-dependencies`: The BOM file for the dependencies `pom.xml`. Only generated if `dependencies_maven_coordinates` is set.
* `name-dependencies.publish`: To be executed by `bazel run` to publish the dependencies `pom.xml` to a maven rpo. Only generated if `dependencies_maven_coordinates` is set.
Args:
name: A unique name for this rule.
maven_coordinates: The maven coordinates of this BOM in `groupId:artifactId:version` form.
bom_pom_template: A template used for generating the `pom.xml` of the BOM at `maven_coordinates` (optional)
dependencies_maven_coordinates: The maven coordinates of a dependencies artifact to generate in GAV format. If empty, none will be generated. (optional)
dependencies_pom_template: A template used for generating the `pom.xml` of the dependencies artifact at `dependencies_maven_coordinates` (optional)
java_exports: A list of `java_export` targets that are used to generate the BOM.
"""
fragments = []
labels = [_label(je) for je in java_exports]
# `same_package_label` doesn't exist in Bazel 5, but we still support it
# so we check the version here to call a non-deprecated API in recent
# Bazel versions, or the older (deprecated) API in Bazel 5.
feature_check_label = Label("//:doesnotexistinrulesjvmexternal")
if hasattr(feature_check_label, "same_package_label"):
fragments = [l.same_package_label("%s.bom-fragment" % l.name) for l in labels]
else:
# TODO: Drop this branch once we drop Bazel 5 support
fragments = [l.relative(":%s.bom-fragment" % l.name) for l in labels]
_maven_bom(
name = name,
maven_coordinates = maven_coordinates,
pom_template = bom_pom_template,
fragments = fragments,
tags = tags,
testonly = testonly,
visibility = visibility,
)
maven_publish(
name = "%s.publish" % name,
coordinates = maven_coordinates,
pom = name,
tags = tags,
testonly = testonly,
visibility = visibility,
)
if dependencies_maven_coordinates:
_maven_dependencies_bom(
name = "%s-dependencies" % name,
maven_coordinates = dependencies_maven_coordinates,
pom_template = dependencies_pom_template,
fragments = fragments,
bom_coordinates = maven_coordinates,
tags = tags,
testonly = testonly,
visibility = visibility,
)
maven_publish(
name = "%s-dependencies.publish" % name,
coordinates = dependencies_maven_coordinates,
pom = "%s-dependencies" % name,
tags = tags,
testonly = testonly,
visibility = visibility,
)