From 86e7ab75b34274ed06afba45dcd24fd72a871f11 Mon Sep 17 00:00:00 2001 From: Guice Team Date: Fri, 4 Mar 2022 11:41:38 -0800 Subject: [PATCH] Add osgi_jar bazel rule to generate OSGi bundle. PiperOrigin-RevId: 432496763 --- WORKSPACE | 65 +++++++++++++---- osgi.bzl | 49 +++++++++++++ third_party/java/bnd/BUILD | 6 ++ third_party/java/picocli/BUILD | 6 ++ tools/BUILD | 16 +++++ tools/OsgiWrapper.java | 128 +++++++++++++++++++++++++++++++++ 6 files changed, 257 insertions(+), 13 deletions(-) create mode 100644 osgi.bzl create mode 100644 third_party/java/bnd/BUILD create mode 100644 third_party/java/picocli/BUILD create mode 100644 tools/BUILD create mode 100644 tools/OsgiWrapper.java diff --git a/WORKSPACE b/WORKSPACE index 934f49d8d..0ee21157d 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,12 +1,13 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") RULES_JVM_EXTERNAL_TAG = "4.2" + RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca" http_archive( name = "rules_jvm_external", - strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, ) @@ -42,41 +43,79 @@ maven_install( "org.ow2.asm:asm:9.2", "org.springframework:spring-core:5.3.14", "org.springframework:spring-beans:5.3.14", + "biz.aQute.bnd:bndlib:2.4.0", + "info.picocli:picocli:4.6.3", maven.artifact( - "biz.aQute", "bnd", "0.0.384", testonly = True + "biz.aQute", + "bnd", + "0.0.384", + testonly = True, ), maven.artifact( - "com.google.guava", "guava-testlib", "31.0.1-jre", testonly = True + "com.google.guava", + "guava-testlib", + "31.0.1-jre", + testonly = True, ), maven.artifact( - "com.google.truth", "truth", "0.45", testonly = True + "com.google.truth", + "truth", + "0.45", + testonly = True, ), maven.artifact( - "javax.inject", "javax.inject-tck", "1", testonly = True + "javax.inject", + "javax.inject-tck", + "1", + testonly = True, ), maven.artifact( - "junit", "junit", "4.13.2", testonly = True + "junit", + "junit", + "4.13.2", + testonly = True, ), maven.artifact( - "org.apache.felix", "org.apache.felix.framework", "3.0.5", testonly = True + "org.apache.felix", + "org.apache.felix.framework", + "3.0.5", + testonly = True, ), maven.artifact( - "org.easymock", "easymock", "3.1", testonly = True + "org.easymock", + "easymock", + "3.1", + testonly = True, ), maven.artifact( - "org.hamcrest", "hamcrest", "2.2", testonly = True + "org.hamcrest", + "hamcrest", + "2.2", + testonly = True, ), maven.artifact( - "org.hibernate.javax.persistence", "hibernate-jpa-2.0-api", "1.0.0.Final", testonly = True + "org.hibernate.javax.persistence", + "hibernate-jpa-2.0-api", + "1.0.0.Final", + testonly = True, ), maven.artifact( - "org.hibernate", "hibernate-core", "5.6.3.Final", testonly = True + "org.hibernate", + "hibernate-core", + "5.6.3.Final", + testonly = True, ), maven.artifact( - "org.hsqldb", "hsqldb-j5", "2.0.0", testonly = True + "org.hsqldb", + "hsqldb-j5", + "2.0.0", + testonly = True, ), maven.artifact( - "org.mockito", "mockito-core", "4.2.0", testonly = True + "org.mockito", + "mockito-core", + "4.2.0", + testonly = True, ), ], repositories = [ diff --git a/osgi.bzl b/osgi.bzl new file mode 100644 index 000000000..cf3f32db1 --- /dev/null +++ b/osgi.bzl @@ -0,0 +1,49 @@ +""" Custom rule to generate OSGi Manifest """ + +def _osgi_jar_impl(ctx): + output = ctx.outputs.osgi_jar.path + pom_version = ctx.var.get("pom_version", "LOCAL-SNAPSHOT") + input_jar = ctx.attr.target[JavaInfo].outputs.jars[0].class_jar + classpath_jars = ctx.attr.target[JavaInfo].compilation_info.compilation_classpath + + args = ctx.actions.args() + args.add_joined("--classpath", classpath_jars, join_with = ":") + args.add("--output_jar", output) + args.add("--bundle_version", pom_version) + args.add("--bundle_name", ctx.attr.bundle_name) + args.add("--symbolic_name", ctx.attr.symbolic_name) + if ctx.attr.fragment: + args.add("--fragment") + args.add_joined("--import_package", ctx.attr.import_package, join_with = ",") + args.add_joined("--export_package", ctx.attr.export_package, join_with = ",") + args.add("--input_jar", input_jar.path) + + ctx.actions.run( + inputs = [input_jar] + classpath_jars.to_list(), + executable = ctx.executable._osgi_wrapper_exe, + arguments = [args], + outputs = [ctx.outputs.osgi_jar], + progress_message = "Generating OSGi bundle Manifest for %s" % ctx.attr.target.label, + ) + +osgi_jar = rule( + attrs = { + "target": attr.label(), + "export_package": attr.string_list(), + "import_package": attr.string_list(), + "bundle_name": attr.string(), + "fragment": attr.bool(), + "symbolic_name": attr.string(), + "_osgi_wrapper_exe": attr.label( + executable = True, + cfg = "host", + allow_files = True, + default = Label("//tools:osgi_wrapper"), + ), + }, + fragments = ["java"], + outputs = { + "osgi_jar": "lib%{name}.jar", + }, + implementation = _osgi_jar_impl, +) diff --git a/third_party/java/bnd/BUILD b/third_party/java/bnd/BUILD new file mode 100644 index 000000000..39a12a811 --- /dev/null +++ b/third_party/java/bnd/BUILD @@ -0,0 +1,6 @@ +package(default_visibility = ["//:src"]) + +alias( + name = "bndlib", + actual = "@maven//:biz_aQute_bnd_bndlib", +) diff --git a/third_party/java/picocli/BUILD b/third_party/java/picocli/BUILD new file mode 100644 index 000000000..ff510c3fc --- /dev/null +++ b/third_party/java/picocli/BUILD @@ -0,0 +1,6 @@ +package(default_visibility = ["//:src"]) + +alias( + name = "picocli", + actual = "@maven//:info_picocli_picocli", +) diff --git a/tools/BUILD b/tools/BUILD new file mode 100644 index 000000000..f8a468717 --- /dev/null +++ b/tools/BUILD @@ -0,0 +1,16 @@ +load("@rules_java//java:defs.bzl", "java_binary") + +package( + default_visibility = ["//:src"], +) + +java_binary( + name = "osgi_wrapper", + srcs = ["OsgiWrapper.java"], + main_class = "com.google.inject.tools.OsgiWrapper", + deps = [ + "//third_party/java/bnd:bndlib", + "//third_party/java/guava/io", + "//third_party/java/picocli", + ], +) diff --git a/tools/OsgiWrapper.java b/tools/OsgiWrapper.java new file mode 100644 index 000000000..24d659501 --- /dev/null +++ b/tools/OsgiWrapper.java @@ -0,0 +1,128 @@ +package com.google.inject.tools; + +import aQute.bnd.osgi.Analyzer; +import aQute.bnd.osgi.Jar; +import java.io.File; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.jar.Manifest; +import java.util.stream.Collectors; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +/** Java binary that runs bndlib to analyze a jar file to generate OSGi bundle manifest. */ +@Command(name = "osgi_wrapper") +public final class OsgiWrapper implements Callable { + private static final String REMOVEHEADERS = + Arrays.stream( + new String[] { + "Embed-Dependency", + "Embed-Transitive", + "Built-By", + "Tool", + "Created-By", + "Build-Jdk", + "Originally-Created-By", + "Archiver-Version", + "Include-Resource", + "Private-Package", + "Ignore-Package", + "Bnd-LastModified" + }) + .collect(Collectors.joining(",")); + + private static final String DESCRIPTION = + "Guice is a lightweight dependency injection framework for Java 8 and above"; + + private static final String COPYRIGHT = "Copyright (C) 2022 Google Inc."; + + private static final String DOC_URL = "https://github.com/google/guice"; + + @Option( + names = {"--input_jar"}, + description = "The jar file to wrap with OSGi metadata") + private File inputJar; + + @Option( + names = {"--output_jar"}, + description = "Output path to the wrapped jar") + private File outputJar; + + @Option( + names = {"--classpath"}, + description = "The classpath that contains dependencies of the input jar, separated with :") + private String classpath; + + @Option( + names = {"--bundle_name"}, + description = "The name of the bundle") + private String bundleName; + + @Option( + names = {"--import_package"}, + description = "The imported packages from this bundle") + private String importPackage; + + @Option( + names = {"--fragment"}, + description = "Whether this bundle is a fragment") + private boolean fragment; + + @Option( + names = {"--export_package"}, + description = "The exported packages from this bundle") + private String exportPackage; + + @Option( + names = {"--symbolic_name"}, + description = "The symbolic name of the bundle") + private String symbolicName; + + @Option( + names = {"--bundle_version"}, + description = "The version of the bundle") + private String bundleVersion; + + @Override + public Integer call() throws Exception { + Analyzer analyzer = new Analyzer(); + Jar bin = new Jar(inputJar); + + analyzer.setJar(bin); + analyzer.setProperty(Analyzer.BUNDLE_NAME, bundleName); + analyzer.setProperty(Analyzer.BUNDLE_SYMBOLICNAME, symbolicName); + analyzer.setProperty(Analyzer.BUNDLE_VERSION, bundleVersion); + analyzer.setProperty(Analyzer.IMPORT_PACKAGE, importPackage); + analyzer.setProperty(Analyzer.EXPORT_PACKAGE, exportPackage); + if (fragment) { + analyzer.setProperty(Analyzer.FRAGMENT_HOST, "com.google.inject"); + } + analyzer.setProperty(Analyzer.NOUSES, "true"); + analyzer.setProperty(Analyzer.BUNDLE_DESCRIPTION, DESCRIPTION); + analyzer.setProperty(Analyzer.BUNDLE_COPYRIGHT, COPYRIGHT); + analyzer.setProperty(Analyzer.BUNDLE_DOCURL, DOC_URL); + analyzer.setProperty(Analyzer.REMOVEHEADERS, REMOVEHEADERS); + + for (String dep : Arrays.asList(classpath.split(":"))) { + analyzer.addClasspath(new File(dep)); + } + + analyzer.analyze(); + + Manifest manifest = analyzer.calcManifest(); + + if (analyzer.isOk()) { + analyzer.getJar().setManifest(manifest); + if (analyzer.save(outputJar, true)) { + return 0; + } + } + return 1; + } + + public static void main(String[] args) { + int exitCode = new CommandLine(new OsgiWrapper()).execute(args); + System.exit(exitCode); + } +}