Skip to content

Commit

Permalink
Give Kotlin jars an OSGi Manifest (#18812)
Browse files Browse the repository at this point in the history
Extend our Java OSGi library to have a version that works for Kotlin. Add a `protobuf_versioned_kt_jvm_library` that creates a bundle with the OSGi manifest and call that instead of `kt_jvm_library` for all our kotlin maven release targets.

Closes #18812

COPYBARA_INTEGRATE_REVIEW=#18812 from deannagarcia:kotlinOSGi 81bab06
PiperOrigin-RevId: 686220820
  • Loading branch information
deannagarcia committed Oct 16, 2024
1 parent 59511be commit 0c51eba
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 10 deletions.
61 changes: 61 additions & 0 deletions build_defs/kotlin_opts.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Protobuf-specific kotlin build rules."""

load("//:protobuf_version.bzl", "PROTOBUF_JAVA_VERSION")
load("//java/osgi:kotlin_osgi.bzl", "osgi_kt_jvm_library")

BUNDLE_DOC_URL = "https://developers.google.com/protocol-buffers/"
BUNDLE_LICENSE = "https://opensource.org/licenses/BSD-3-Clause"

def protobuf_versioned_kt_jvm_library(
automatic_module_name,
bundle_description,
bundle_name,
bundle_symbolic_name,
bundle_additional_imports = [],
bundle_additional_exports = [],
**kwargs):
"""Extends `kt_jvm_library` to add OSGi headers to the MANIFEST.MF using bndlib
This macro should be usable as a drop-in replacement for kt_jvm_library.
The additional arguments are given the bndlib tool to generate an OSGi-compliant manifest file.
See [bnd documentation](https://bnd.bndtools.org/chapters/110-introduction.html)
Takes all the args that are standard for a kt_jvm_library target plus the following.
Args:
bundle_description: (required) The Bundle-Description header defines a short
description of this bundle.
automatic_module_name: (required) The Automatic-Module-Name header that represents
the name of the module when this bundle is used as an automatic
module.
bundle_name: (required) The Bundle-Name header defines a readable name for this
bundle. This should be a short, human-readable name that can
contain spaces.
bundle_symbolic_name: (required) The Bundle-SymbolicName header specifies a
non-localizable name for this bundle. The bundle symbolic name
together with a version must identify a unique bundle though it can
be installed multiple times in a framework. The bundle symbolic
name should be based on the reverse domain name convention.
bundle_additional_exports: The Export-Package header contains a
declaration of exported packages. These are additional export
package statements to be added before the default wildcard export
"*;version={$Bundle-Version}".
bundle_additional_imports: The Import-Package header declares the
imported packages for this bundle. These are additional import
package statements to be added before the default wildcard import
"*".
**kwargs: Additional key-word arguments that are passed to the internal
kt_jvm_library target.
"""
osgi_kt_jvm_library(
automatic_module_name = automatic_module_name,
bundle_doc_url = BUNDLE_DOC_URL,
bundle_license = BUNDLE_LICENSE,
bundle_version = PROTOBUF_JAVA_VERSION,
bundle_description = bundle_description,
bundle_name = bundle_name,
bundle_symbolic_name = bundle_symbolic_name,
bundle_additional_exports = bundle_additional_exports,
bundle_additional_imports = bundle_additional_imports + ["sun.misc;resolution:=optional"],
**kwargs
)
25 changes: 20 additions & 5 deletions java/kotlin-lite/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix")
load("//:protobuf.bzl", "internal_gen_kt_protos")
load("//:protobuf_version.bzl", "PROTOBUF_JAVA_VERSION")
load("//bazel:java_lite_proto_library.bzl", "java_lite_proto_library")
load("//build_defs:kotlin_opts.bzl", "protobuf_versioned_kt_jvm_library")

java_lite_proto_library(
name = "example_extensible_message_java_proto_lite",
Expand Down Expand Up @@ -47,6 +48,24 @@ kt_jvm_library(
],
)

protobuf_versioned_kt_jvm_library(
name = "kotlin-lite_bundle",
automatic_module_name = "com.google.protobuf",
bundle_description = "Kotlin lite Protocol Buffers library. Protocol " +
"Buffers are a way of encoding structured data in " +
"an efficient yet extensible format.",
bundle_name = "Protocol Buffers [Kotlin-Lite]",
bundle_symbolic_name = "com.google.protobuf",
visibility = ["//visibility:public"],
exports = [
":lite_extensions",
":well_known_protos_kotlin_lite",
"//java/kotlin:bytestring_lib",
"//java/kotlin:only_for_use_in_proto_generated_code_its_generator_and_tests",
"//java/kotlin:shared_runtime",
],
)

kt_jvm_export(
name = "kotlin-lite_mvn",
deploy_env = [
Expand All @@ -62,11 +81,7 @@ kt_jvm_export(
],
tags = ["manual"],
runtime_deps = [
":lite_extensions",
":well_known_protos_kotlin_lite",
"//java/kotlin:bytestring_lib",
"//java/kotlin:only_for_use_in_proto_generated_code_its_generator_and_tests",
"//java/kotlin:shared_runtime",
":kotlin-lite_bundle",
],
)

Expand Down
25 changes: 20 additions & 5 deletions java/kotlin/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ load("//:protobuf.bzl", "internal_gen_kt_protos")
load("//:protobuf_version.bzl", "PROTOBUF_JAVA_VERSION")
load("//bazel:java_proto_library.bzl", "java_proto_library")
load("//bazel:proto_library.bzl", "proto_library")
load("//build_defs:kotlin_opts.bzl", "protobuf_versioned_kt_jvm_library")

exports_files([
"src/test/kotlin/com/google/protobuf/Proto3Test.kt",
Expand Down Expand Up @@ -50,6 +51,24 @@ kt_jvm_library(
deps = ["//java/core"],
)

protobuf_versioned_kt_jvm_library(
name = "kotlin_bundle",
automatic_module_name = "com.google.protobuf",
bundle_description = "Kotlin core Protocol Buffers library. Protocol " +
"Buffers are a way of encoding structured data in an" +
"efficient yet extensible format.",
bundle_name = "Protocol Buffers [Kotlin-Core]",
bundle_symbolic_name = "com.google.protobuf",
visibility = ["//visibility:public"],
exports = [
":bytestring_lib",
":full_extensions",
":only_for_use_in_proto_generated_code_its_generator_and_tests",
":shared_runtime",
":well_known_protos_kotlin",
],
)

kt_jvm_export(
name = "kotlin_mvn",
deploy_env = [
Expand All @@ -65,11 +84,7 @@ kt_jvm_export(
],
tags = ["manual"],
runtime_deps = [
":bytestring_lib",
":full_extensions",
":only_for_use_in_proto_generated_code_its_generator_and_tests",
":shared_runtime",
":well_known_protos_kotlin",
":kotlin_bundle",
],
)

Expand Down
215 changes: 215 additions & 0 deletions java/osgi/kotlin_osgi.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
""" Custom rule to generate OSGi Manifest for Kotlin """

load("@rules_java//java:defs.bzl", "JavaInfo")
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")

def osgi_kt_jvm_library(
name,
automatic_module_name,
bundle_description,
bundle_doc_url,
bundle_license,
bundle_name,
bundle_symbolic_name,
bundle_version,
bundle_additional_imports = [],
bundle_additional_exports = [],
deps = [],
exports = [],
exported_plugins = [],
neverlink = False,
runtime_deps = [],
visibility = [],
**kwargs):
"""Extends `kt_jvm_library` to add OSGi headers to the MANIFEST.MF using bndlib
This macro should be usable as a drop-in replacement for kt_jvm_library.
The additional arguments are given the bndlib tool to generate an OSGi-compliant manifest file.
See [bnd documentation](https://bnd.bndtools.org/chapters/110-introduction.html)
Args:
name: (required) A unique name for this target.
automatic_module_name: (required) The Automatic-Module-Name header that represents
the name of the module when this bundle is used as an automatic
module.
bundle_description: (required) The Bundle-Description header defines a short
description of this bundle.
bundle_doc_url: (required) The Bundle-DocURL headers must contain a URL pointing
to documentation about this bundle.
bundle_license: (required) The Bundle-License header provides an optional machine
readable form of license information.
bundle_name: (required) The Bundle-Name header defines a readable name for this
bundle. This should be a short, human-readable name that can
contain spaces.
bundle_symbolic_name: (required) The Bundle-SymbolicName header specifies a
non-localizable name for this bundle. The bundle symbolic name
together with a version must identify a unique bundle though it can
be installed multiple times in a framework. The bundle symbolic
name should be based on the reverse domain name convention.
bundle_version: (required) The Bundle-Version header specifies the version string
for this bundle. The version string is expected to follow semantic
versioning conventions MAJOR.MINOR.PATCH[.BUILD]
bundle_additional_exports: The Export-Package header contains a
declaration of exported packages. These are additional export
package statements to be added before the default wildcard export
"*;version={$Bundle-Version}".
bundle_additional_imports: The Import-Package header declares the
imported packages for this bundle. These are additional import
package statements to be added before the default wildcard import
"*".
deps: The list of libraries to link into this library. See general
comments about deps at Typical attributes defined by most build
rules. The jars built by java_library rules listed in deps will be
on the compile-time classpath of this rule. Furthermore the
transitive closure of their deps, runtime_deps and exports will be
on the runtime classpath. By contrast, targets in the data
attribute are included in the runfiles but on neither the
compile-time nor runtime classpath.
exports: Exported libraries.
exported_plugins: The list of java_plugins (e.g. annotation processors)
to export to libraries that directly depend on this library. The
specified list of java_plugins will be applied to any library which
directly depends on this library, just as if that library had
explicitly declared these labels in plugins.
neverlink: Whether this library should only be used for compilation and
not at runtime. Useful if the library will be provided by the runtime
environment during execution. Examples of such libraries are the IDE
APIs for IDE plug-ins or tools.jar for anything running on a standard
JDK.
runtime_deps: Libraries to make available to the final binary or test
at runtime only. Like ordinary deps, these will appear on the runtime
classpath, but unlike them, not on the compile-time classpath.
Dependencies needed only at runtime should be listed here.
Dependency-analysis tools should ignore targets that appear in both
runtime_deps and deps
visibility: The visibility attribute on a target controls whether the
target can be used in other packages. See the documentation for
visibility.
**kwargs: Additional key-word arguments that are passed to the internal
java_library target.
"""

# Build the private jar without the OSGI manifest
private_library_name = "%s-no-manifest-do-not-use" % name
kt_jvm_library(
name = private_library_name,
deps = deps,
runtime_deps = runtime_deps,
neverlink = True,
visibility = ["//visibility:private"],
**kwargs
)

# Repackage the jar with an OSGI manifest
_osgi_kt_jvm_jar(
name = name,
automatic_module_name = automatic_module_name,
bundle_description = bundle_description,
bundle_doc_url = bundle_doc_url,
bundle_license = bundle_license,
bundle_name = bundle_name,
bundle_symbolic_name = bundle_symbolic_name,
bundle_version = bundle_version,
export_package = bundle_additional_exports + ["*;version=${Bundle-Version}"],
import_package = bundle_additional_imports + ["*"],
target = private_library_name,
deps = deps,
runtime_deps = runtime_deps,
exported_plugins = exported_plugins,
neverlink = neverlink,
exports = exports,
visibility = visibility,
)

def _run_osgi_wrapper(ctx, input_jar, output_jar):
args = ctx.actions.args()
args.add("--input_jar", input_jar.path)
args.add("--output_jar", output_jar.path)
args.add("--automatic_module_name", ctx.attr.automatic_module_name)
args.add("--bundle_copyright", ctx.attr.bundle_copyright)
args.add("--bundle_description", ctx.attr.bundle_description)
args.add("--bundle_doc_url", ctx.attr.bundle_doc_url)
args.add("--bundle_license", ctx.attr.bundle_license)
args.add("--bundle_name", ctx.attr.bundle_name)
args.add("--bundle_version", ctx.attr.bundle_version)
args.add("--bundle_symbolic_name", ctx.attr.bundle_symbolic_name)
args.add_joined("--export_package", ctx.attr.export_package, join_with = ",")
args.add_joined("--import_package", ctx.attr.import_package, join_with = ",")

ctx.actions.run(
inputs = [input_jar],
executable = ctx.executable._osgi_wrapper_exe,
arguments = [args],
outputs = [output_jar],
progress_message = "Generating OSGi bundle Manifest for %s" % input_jar.path,
)

# Kotlin implementation of osgi jar, removes classpath and source_jar
def _osgi_kt_jvm_jar_impl(ctx):
if len(ctx.attr.target[JavaInfo].java_outputs) != 1:
fail("osgi_jar rule can only be used on a single java target.")
target_java_output = ctx.attr.target[JavaInfo].java_outputs[0]

output_jar = ctx.outputs.output_jar

input_jar = target_java_output.class_jar

_run_osgi_wrapper(ctx, input_jar, output_jar)

return [
DefaultInfo(
files = depset([output_jar]),
# Workaround for https://github.com/bazelbuild/bazel/issues/15043
# Bazel's native rule such as sh_test do not pick up 'files' in
# DefaultInfo for a target in 'data'.
data_runfiles = ctx.runfiles([output_jar]),
),
JavaInfo(
output_jar = output_jar,

# compile_jar should be an ijar, but using an ijar results in
# missing protobuf import version.
compile_jar = output_jar,
generated_class_jar = target_java_output.generated_class_jar,
native_headers_jar = target_java_output.native_headers_jar,
manifest_proto = target_java_output.manifest_proto,
neverlink = ctx.attr.neverlink,
deps = [dep[JavaInfo] for dep in ctx.attr.deps],
runtime_deps = [dep[JavaInfo] for dep in ctx.attr.runtime_deps],
exports = [exp[JavaInfo] for exp in ctx.attr.exports],
exported_plugins = ctx.attr.exported_plugins,
),
]

_osgi_kt_jvm_jar = rule(
implementation = _osgi_kt_jvm_jar_impl,
outputs = {
"output_jar": "lib%{name}.jar",
},
attrs = {
"automatic_module_name": attr.string(),
"bundle_copyright": attr.string(),
"bundle_description": attr.string(),
"bundle_doc_url": attr.string(),
"bundle_license": attr.string(),
"bundle_name": attr.string(),
"bundle_version": attr.string(),
"bundle_symbolic_name": attr.string(),
"export_package": attr.string_list(),
"import_package": attr.string_list(),
"target": attr.label(),
"deps": attr.label_list(),
"runtime_deps": attr.label_list(),
"exports": attr.label_list(),
"neverlink": attr.bool(),
"exported_plugins": attr.label_list(),
"_osgi_wrapper_exe": attr.label(
executable = True,
cfg = "exec",
allow_files = True,
default = Label("//java/osgi:osgi_wrapper"),
),
},
)

0 comments on commit 0c51eba

Please sign in to comment.