From 0c51ebac875657ecf6f8cca3d5463c23eb7d8b00 Mon Sep 17 00:00:00 2001 From: deannagarcia <69992229+deannagarcia@users.noreply.github.com> Date: Tue, 15 Oct 2024 13:33:45 -0700 Subject: [PATCH] Give Kotlin jars an OSGi Manifest (#18812) 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=https://github.com/protocolbuffers/protobuf/pull/18812 from deannagarcia:kotlinOSGi 81bab06f61983c6aff6d3d009faf76c99371a358 PiperOrigin-RevId: 686220820 --- build_defs/kotlin_opts.bzl | 61 ++++++++++ java/kotlin-lite/BUILD.bazel | 25 +++- java/kotlin/BUILD.bazel | 25 +++- java/osgi/kotlin_osgi.bzl | 215 +++++++++++++++++++++++++++++++++++ 4 files changed, 316 insertions(+), 10 deletions(-) create mode 100644 build_defs/kotlin_opts.bzl create mode 100644 java/osgi/kotlin_osgi.bzl diff --git a/build_defs/kotlin_opts.bzl b/build_defs/kotlin_opts.bzl new file mode 100644 index 000000000000..3a811170d9c6 --- /dev/null +++ b/build_defs/kotlin_opts.bzl @@ -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 + ) diff --git a/java/kotlin-lite/BUILD.bazel b/java/kotlin-lite/BUILD.bazel index f223451b1873..493ba1af911c 100644 --- a/java/kotlin-lite/BUILD.bazel +++ b/java/kotlin-lite/BUILD.bazel @@ -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", @@ -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 = [ @@ -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", ], ) diff --git a/java/kotlin/BUILD.bazel b/java/kotlin/BUILD.bazel index 7faced67159c..d0fe25fbb9aa 100644 --- a/java/kotlin/BUILD.bazel +++ b/java/kotlin/BUILD.bazel @@ -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", @@ -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 = [ @@ -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", ], ) diff --git a/java/osgi/kotlin_osgi.bzl b/java/osgi/kotlin_osgi.bzl new file mode 100644 index 000000000000..2885287ec838 --- /dev/null +++ b/java/osgi/kotlin_osgi.bzl @@ -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"), + ), + }, +)