blob: eba7dce4971c1d21ddbb8d2e6d6a311506d6b95a [file]
"""
Build rules for plugins.
"""
load("@rules_java//java:defs.bzl", "java_binary", "java_library")
load("@rules_jvm_external//:defs.bzl", "artifact")
load(
"//tools:commons.bzl",
_plugin_deps = "PLUGIN_DEPS",
_plugin_deps_neverlink = "PLUGIN_DEPS_NEVERLINK",
_plugin_test_deps = "PLUGIN_TEST_DEPS",
)
load("//tools:genrule2.bzl", "genrule2")
load("//tools:junit.bzl", "junit_tests")
load("//tools:in_gerrit_tree.bzl", "in_gerrit_tree_enabled")
load("//tools:runtime_jars_allowlist.bzl", "runtime_jars_allowlist_test")
load("//tools:runtime_jars_overlap.bzl", "runtime_jars_overlap_test")
"""Bazel rule for building [Gerrit Code Review](https://www.gerritcodereview.com/)
gerrit_plugin is rule for building Gerrit plugins using Bazel.
"""
PLUGIN_DEPS = _plugin_deps
PLUGIN_DEPS_NEVERLINK = _plugin_deps_neverlink
PLUGIN_TEST_DEPS = _plugin_test_deps
def gerrit_api_neverlink(name):
"""Return the correct Gerrit API neverlink dependency for the current build mode."""
if not native.module_name():
# Gerrit and/or plugin does not use bazel modules yet; use Gerrit API from
# maven repository as defined in gerrit_api.bzl
# TODO(thomas): Remove after migration to Bazel modules is complete
return PLUGIN_DEPS_NEVERLINK
elif native.module_name() == "gerrit":
# In-tree build, i.e. Gerrit is the main module; use Gerrit API from Gerrit
# source tree
return ["//plugins:plugin-lib-neverlink"]
else:
# Standalone build; use Gerrit API from maven repository as defined in
# plugin's module
java_library(
name = name + "-gerrit-api-neverlink",
neverlink = 1,
visibility = ["//visibility:public"],
exports = ["@external_plugin_deps//:com_google_gerrit_gerrit_plugin_api"],
)
return [":" + name + "-gerrit-api-neverlink"]
def gerrit_api():
"""Return the correct Gerrit API dependency for the current build mode."""
if not native.module_name():
# Gerrit and/or plugin does not use bazel modules yet; use Gerrit API from
# maven repository as defined in gerrit_api.bzl
# TODO(thomas): Remove after migration to Bazel modules is complete
return PLUGIN_DEPS
elif native.module_name() == "gerrit":
# In-tree build, i.e. Gerrit is the main module; use Gerrit API from Gerrit
# source tree
return ["//plugins:plugin-lib"]
else:
# Standalone build; use Gerrit API from maven repository as defined in
# plugin's module
return ["@external_plugin_deps//:com_google_gerrit_gerrit_plugin_api"]
def gerrit_acceptance_framework():
"""
Return the correct Gerrit Acceptance Framework dependency for the current
build mode.
"""
if not native.module_name():
# Gerrit and/or plugin does not use bazel modules yet; use Gerrit API from
# maven repository as defined in gerrit_api.bzl
# TODO(thomas): Remove after migration to Bazel modules is complete
return PLUGIN_TEST_DEPS
elif native.module_name() == "gerrit":
# In-tree build, i.e. Gerrit is the main module; use Gerrit API from Gerrit
# source tree
return ["//java/com/google/gerrit/acceptance:lib"]
else:
# Standalone build; use Gerrit API from maven repository as defined in
# plugin's module
return ["@external_plugin_deps//:com_google_gerrit_gerrit_acceptance_framework"]
def _artifacts(coords, repository_name):
"""Convert Maven coordinates to Bazel labels in the given external repo.
Args:
coords: List of Maven coordinates, for example
`["com.github.scribejava:scribejava-core"]`.
repository_name: Name of the rules_jvm_external repository that exports
the generated labels, for example `"oauth_plugin_deps"`.
Returns:
List of Bazel labels corresponding to the given Maven coordinates.
"""
return [artifact(c, repository_name = repository_name) for c in coords]
def gerrit_plugin(
name = None,
plugin = None,
deps = [],
ext_deps = [],
ext_repo = None,
srcs = [],
resources = [],
resource_jars = [],
runtime_deps = [],
manifest_entries = [],
dir_name = None,
license = None,
target_suffix = "",
deploy_env = [],
**kwargs):
"""Builds a Gerrit plugin.
Args:
name: The name of the plugin target.
plugin: Backward-compatible alias for `name`.
deps: List of additional Bazel dependencies for the plugin.
ext_deps: List of Maven coordinates for external dependencies.
ext_repo: Name of the external repository generated by rules_jvm_external.
Defaults to `<name>_plugin_deps`.
srcs: List of Java source files for the plugin.
resources: List of resource files to be included in the plugin JAR.
resource_jars: List of JARs containing resources.
runtime_deps: List of runtime Bazel dependencies.
manifest_entries: List of additional lines to add to the plugin's manifest file.
dir_name: The directory name for the plugin, used in stamping. Defaults to `name`.
license: Optional plugin-owned license file to package as `META-INF/LICENSE`.
target_suffix: Suffix to append to the final plugin JAR name.
deploy_env: List of java_binary targets representing the runtime/deployment
environment that will load this plugin. Dependencies shared with these
targets are excluded from this binary's runtime classpath and deploy jar.
**kwargs: Additional arguments passed to the underlying `java_library` and `java_binary` rules.
This rule creates a deployable .jar file for a Gerrit plugin."""
if name == None:
name = plugin
elif plugin != None and plugin != name:
fail("gerrit_plugin: `name` and `plugin` must match if both are set")
if name == None:
fail("gerrit_plugin: one of `name` or `plugin` must be set")
if ext_repo == None:
ext_repo = name + "_plugin_deps"
deps = deps + _artifacts(ext_deps, ext_repo)
java_library(
name = name + "__plugin",
srcs = srcs,
resources = resources,
deps = deps + gerrit_api_neverlink(name),
runtime_deps = runtime_deps,
visibility = ["//visibility:public"],
**kwargs
)
if not dir_name:
dir_name = name
java_binary(
name = "%s__non_stamped" % name,
deploy_manifest_lines = manifest_entries + ["Gerrit-ApiType: plugin"],
main_class = "Dummy",
runtime_deps = [
":%s__plugin" % name,
] + runtime_deps + resource_jars,
deploy_env = deploy_env,
visibility = ["//visibility:public"],
**kwargs
)
native.genrule(
name = name + "__gen_stamp_info",
stamp = 1,
cmd = "cat bazel-out/stable-status.txt | grep \"^STABLE_BUILD_%s_LABEL\" | awk '{print $$NF}' > $@" % dir_name.upper(),
outs = ["%s__gen_stamp_info.txt" % name],
)
# TODO(davido): Remove manual merge of manifest file when this feature
# request is implemented: https://github.com/bazelbuild/bazel/issues/2009
# TODO(davido): Remove manual touch command when this issue is resolved:
# https://github.com/bazelbuild/bazel/issues/10789
license_tools = [license] if license else []
copy_license_cmd = (
"mkdir -p META-INF && cp -f $$ROOT/$(location %s) META-INF/LICENSE" % license if license else "true"
)
EXCLUDES = " ".join([
"'META-INF/%s'" % p
for p in [
"LICENSE",
"LICENSE.txt",
"NOTICE",
"NOTICE.txt",
"license",
"license/*",
"notice",
"notice/*",
]
])
genrule2(
name = name + target_suffix,
stamp = 1,
srcs = ["%s__non_stamped_deploy.jar" % name],
cmd = " && ".join([
"TZ=UTC",
"export TZ",
"GEN_VERSION=$$(cat $(location :%s__gen_stamp_info))" % name,
"API_VERSION=$$(cat $(location @gerrit_api_version//:version.txt))",
"cd $$TMP",
"unzip -qo $$ROOT/$< -x " + EXCLUDES + " 2>/dev/null",
copy_license_cmd,
"echo \"Implementation-Version: $$GEN_VERSION\nGerrit-ApiVersion: $$API_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF",
"find . -exec touch '{}' ';'",
"zip -Xqr $$ROOT/$@ .",
]),
tools = [
":%s__gen_stamp_info" % name,
"@gerrit_api_version//:version.txt",
] + license_tools,
outs = ["%s%s.jar" % (name, target_suffix)],
visibility = ["//visibility:public"],
)
def gerrit_plugin_tests(
name = None,
srcs = [],
deps = [],
plugin = "",
ext_deps = [],
ext_repo = None,
tags = None,
exports = [],
skip_dependency_tests = False,
dependency_test_name = None,
dependency_test_allowlist = None,
dependency_test_overlap_against = None,
**kwargs):
"""Runs junit tests for a Gerrit plugin.
Args:
name: Name of the junit test target. Defaults to `<plugin>_tests`.
srcs: List of Java source files for the plugin tests.
deps: List of additional Bazel dependencies for the test target.
plugin: Name of the plugin under test. Only required if `exports`,
`ext_deps`, or automatic plugin target wiring is used.
Only either `ext_deps` or `exports` is allowed.
ext_deps: List of Maven coordinates for external test dependencies.
When set, dependency tests are generated automatically.
ext_repo: Name of the external repository generated by rules_jvm_external.
Defaults to `<plugin>_plugin_deps`.
tags: Optional list of tags for the test target. If `plugin` is set, it
is added automatically if not already present.
exports: List of targets to export for in-tree testing. Must be used together
with `plugin` argument. Targets will also be added as dependencies to
the test target created by this rule. Deprecated in favor of `ext_deps`.
Only either `ext_deps` or `exports` is allowed.
skip_dependency_tests: Whether to skip generating dependency tests when
`ext_deps` and `plugin` are set. Defaults to `False`.
dependency_test_name: Name of the generated dependency test suite.
Defaults to `<plugin>_dependency_tests`.
dependency_test_allowlist: Optional allowlist passed to
`gerrit_plugin_dependency_tests()`.
dependency_test_overlap_against: Optional overlap manifest passed to
`gerrit_plugin_dependency_tests()`.
**kwargs: Additional arguments passed to the underlying `junit_tests` rule.
"""
if ext_deps and exports:
fail("Only either provide `exports` (deprecated) or `ext_deps` (recommended).")
if plugin:
if name == None:
name = plugin + "_tests"
if ext_repo == None:
ext_repo = plugin + "_plugin_deps"
elif name == None:
fail("gerrit_plugin_tests: `name` must be set when `plugin` is empty")
if tags == None:
tags = []
if plugin and plugin not in tags:
tags = tags + [plugin]
if plugin:
deps = [":%s__plugin" % plugin] + deps
if ext_deps or exports:
if not plugin:
fail("gerrit_plugin_tests: `plugin` must be set when `ext_deps` or `exports` is provided")
if ext_deps:
exports = _artifacts(ext_deps, ext_repo)
java_library(
name = plugin + "__plugin_test_deps",
testonly = True,
visibility = ["//visibility:public"],
exports = exports,
)
deps = deps + [":" + plugin + "__plugin_test_deps"]
junit_tests(
name = name,
srcs = srcs,
deps = deps + gerrit_api() + gerrit_acceptance_framework(),
tags = tags,
**kwargs
)
if not skip_dependency_tests and ext_deps and plugin:
if dependency_test_name == None:
dependency_test_name = plugin + "_dependency_tests"
gerrit_plugin_dependency_tests(
plugin = plugin,
name = dependency_test_name,
allowlist = dependency_test_allowlist,
overlap_against = dependency_test_overlap_against,
)
def gerrit_plugin_test_util(
name,
srcs = [],
deps = [],
**kwargs):
"""Creates a test utility library for a Gerrit plugin.
This is intended for code that is only used by tests and should not be
included in the plugin JAR.
Args:
name: The name of the test utility library.
deps: List of additional dependencies for the test utility library.
srcs: List of Java source files for the test utility library.
**kwargs: Additional arguments passed to the underlying `java_library` rule.
"""
java_library(
name = name,
testonly = True,
srcs = srcs,
deps = deps + gerrit_api_neverlink(name) + gerrit_acceptance_framework(),
**kwargs
)
def gerrit_plugin_dependency_tests(
plugin,
name = "dependency_tests",
allowlist = None,
overlap_against = None):
"""Generates runtime JAR safety tests for a Gerrit plugin.
Targets the `:{plugin}__plugin` library created by `gerrit_plugin()`, so
the `plugin` argument must match the `name` passed to `gerrit_plugin()`.
Always creates two test targets:
- `{plugin}_dependency_allowlist_test`: verifies the set of bundled
third-party JARs exactly matches the allowlist.
- `{plugin}_dependency_overlap_test`: verifies the plugin does not bundle
JARs already shipped by Gerrit at runtime (automatically skipped in
standalone plugin workspaces; see `in_gerrit_tree_enabled()`).
Both targets are discovered by `bazelisk test //plugins/{plugin}/...`.
Args:
plugin: Plugin name as passed to `gerrit_plugin()`. Used to derive the
checked target `:{plugin}__plugin` and to name the generated
test targets.
name: Unused; accepted for API compatibility. Defaults to
"dependency_tests".
allowlist: Label of a text file listing the expected bundled third-party
JAR IDs, one per line. Defaults to
`:{plugin}_third_party_runtime_jars.allowlist.txt`.
Refresh it by building the corresponding manifest target and
copying its output over the allowlist file.
overlap_against: Label of a JAR-ID manifest to check for overlap (e.g.
the Gerrit WAR's `//:headless.war.jars.txt`). Defaults
to `//:headless.war.jars.txt`.
Example:
load(
"@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
"gerrit_plugin",
"gerrit_plugin_dependency_tests",
)
gerrit_plugin(
name = "my-plugin",
srcs = glob(["src/main/java/**/*.java"]),
manifest_entries = [
"Gerrit-PluginName: my-plugin",
"Gerrit-Module: com.example.MyModule",
],
)
gerrit_plugin_dependency_tests(
plugin = "my-plugin",
# Optional: supply a custom allowlist or overlap manifest.
# allowlist = ":my_plugin_third_party_runtime_jars.allowlist.txt",
# overlap_against = "//:headless.war.jars.txt",
)
"""
plugin_target = ":%s__plugin" % plugin
if not allowlist:
allowlist = ":%s_third_party_runtime_jars.allowlist.txt" % plugin
runtime_jars_allowlist_test(
name = plugin + "_dependency_allowlist_test",
target = plugin_target,
allowlist = allowlist,
)
if not overlap_against:
overlap_against = "//:headless.war.jars.txt"
runtime_jars_overlap_test(
name = plugin + "_dependency_overlap_test",
target = plugin_target,
against = overlap_against,
target_compatible_with = in_gerrit_tree_enabled(),
)
native.test_suite(
name = name,
tests = [
":" + plugin + "_dependency_allowlist_test",
":" + plugin + "_dependency_overlap_test",
],
)