• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1""" Custom rule to generate OSGi Manifest """
2
3load("@rules_java//java:defs.bzl", "JavaInfo", "java_library")
4
5# Note that this rule is currently agnostic of protobuf concerns and could be
6# pulled out as a general purpose helper to allow migrations from maven to bazel
7# for OSS release builds.
8#
9# There are (at least) 3 things that would nice to fix about this rule:
10# 1. `deps` are captured by wrapping the java_library target into the
11#    osgi_java_library target -- if possible, it would be better to get
12#    the deps from the JavaInfo or some other provider from any java_library
13#    target.
14# 2. imports are probably not being calculated properly for deps that are more
15#    than 1 step deep in the dependency chain. For example: //java:core depends
16#    on //java/core:lite_runtime_only but does not calculate the need for
17#    "sun.misc" like the //java/core:lite target does (even though the same code
18#    is transitively included. Those imports can be explicitly added through
19#    `bundle_additional_imports`, but it would be better if the calculation
20#    applied correctly to transitive dependencies.
21# 3. Versioned imports didn't work properly when an ijar is used as the
22#    "compile_jar". Thus, this rule uses the full jar as the compile_jar,
23#    which is probably sub-optimal.
24def osgi_java_library(
25        name,
26        automatic_module_name,
27        bundle_description,
28        bundle_doc_url,
29        bundle_license,
30        bundle_name,
31        bundle_symbolic_name,
32        bundle_version,
33        bundle_additional_imports = [],
34        bundle_additional_exports = [],
35        deps = [],
36        exports = [],
37        exported_plugins = [],
38        neverlink = False,
39        runtime_deps = [],
40        visibility = [],
41        **kwargs):
42    """Extends `java_library` to add OSGi headers to the MANIFEST.MF using bndlib
43
44    This macro should be usable as a drop-in replacement for java_library.
45
46    The additional arguments are given the bndlib tool to generate an OSGi-compliant manifest file.
47    See [bnd documentation](https://bnd.bndtools.org/chapters/110-introduction.html)
48
49    Args:
50        name: (required) A unique name for this target.
51        bundle_description: (required) The Bundle-Description header defines a short
52            description of this bundle.
53        bundle_doc_url: (required) The Bundle-DocURL headers must contain a URL pointing
54            to documentation about this bundle.
55        bundle_license: (required) The Bundle-License header provides an optional machine
56            readable form of license information.
57        bundle_name: (required) The Bundle-Name header defines a readable name for this
58            bundle. This should be a short, human-readable name that can
59            contain spaces.
60        bundle_symbolic_name: (required) The Bundle-SymbolicName header specifies a
61            non-localizable name for this bundle. The bundle symbolic name
62            together with a version must identify a unique bundle though it can
63            be installed multiple times in a framework. The bundle symbolic
64            name should be based on the reverse domain name convention.
65        bundle_version: (required) The Bundle-Version header specifies the version string
66            for this bundle. The version string is expected to follow semantic
67            versioning conventions MAJOR.MINOR.PATCH[.BUILD]
68        bundle_additional_exports: The Export-Package header contains a
69            declaration of exported packages. These are additional export
70            package statements to be added before the default wildcard export
71            "*;version={$Bundle-Version}".
72        bundle_additional_imports: The Import-Package header declares the
73            imported packages for this bundle. These are additional import
74            package statements to be added before the default wildcard import
75            "*".
76        deps: The list of libraries to link into this library. See general
77            comments about deps at Typical attributes defined by most build
78            rules. The jars built by java_library rules listed in deps will be
79            on the compile-time classpath of this rule. Furthermore the
80            transitive closure of their deps, runtime_deps and exports will be
81            on the runtime classpath. By contrast, targets in the data
82            attribute are included in the runfiles but on neither the
83            compile-time nor runtime classpath.
84        exports: Exported libraries.
85        exported_plugins: The list of java_plugins (e.g. annotation processors)
86            to export to libraries that directly depend on this library. The
87            specified list of java_plugins will be applied to any library which
88            directly depends on this library, just as if that library had
89            explicitly declared these labels in plugins.
90        neverlink: Whether this library should only be used for compilation and
91            not at runtime. Useful if the library will be provided by the runtime
92            environment during execution. Examples of such libraries are the IDE
93            APIs for IDE plug-ins or tools.jar for anything running on a standard
94            JDK.
95        runtime_deps: Libraries to make available to the final binary or test
96            at runtime only. Like ordinary deps, these will appear on the runtime
97            classpath, but unlike them, not on the compile-time classpath.
98            Dependencies needed only at runtime should be listed here.
99            Dependency-analysis tools should ignore targets that appear in both
100            runtime_deps and deps
101        visibility: The visibility attribute on a target controls whether the
102            target can be used in other packages. See the documentation for
103            visibility.
104        **kwargs: Additional key-word arguments that are passed to the internal
105            java_library target.
106    """
107
108    # Build the private jar without the OSGI manifest
109    private_library_name = "%s-no-manifest-do-not-use" % name
110    java_library(
111        name = private_library_name,
112        deps = deps,
113        runtime_deps = runtime_deps,
114        neverlink = True,
115        exported_plugins = exported_plugins,
116        visibility = ["//visibility:private"],
117        **kwargs
118    )
119
120    # Repackage the jar with an OSGI manifest
121    _osgi_jar(
122        name = name,
123        automatic_module_name = automatic_module_name,
124        bundle_description = bundle_description,
125        bundle_doc_url = bundle_doc_url,
126        bundle_license = bundle_license,
127        bundle_name = bundle_name,
128        bundle_symbolic_name = bundle_symbolic_name,
129        bundle_version = bundle_version,
130        export_package = bundle_additional_exports + ["*;version=${Bundle-Version}"],
131        import_package = bundle_additional_imports + ["*"],
132        target = private_library_name,
133        deps = deps,
134        runtime_deps = runtime_deps,
135        exported_plugins = exported_plugins,
136        neverlink = neverlink,
137        exports = exports,
138        visibility = visibility,
139    )
140
141def _run_osgi_wrapper(ctx, input_jar, classpath_jars, output_jar):
142    args = ctx.actions.args()
143    args.add_joined("--classpath", classpath_jars, join_with = ":")
144    args.add("--input_jar", input_jar.path)
145    args.add("--output_jar", output_jar.path)
146    args.add("--automatic_module_name", ctx.attr.automatic_module_name)
147    args.add("--bundle_copyright", ctx.attr.bundle_copyright)
148    args.add("--bundle_description", ctx.attr.bundle_description)
149    args.add("--bundle_doc_url", ctx.attr.bundle_doc_url)
150    args.add("--bundle_license", ctx.attr.bundle_license)
151    args.add("--bundle_name", ctx.attr.bundle_name)
152    args.add("--bundle_version", ctx.attr.bundle_version)
153    args.add("--bundle_symbolic_name", ctx.attr.bundle_symbolic_name)
154    args.add_joined("--export_package", ctx.attr.export_package, join_with = ",")
155    args.add_joined("--import_package", ctx.attr.import_package, join_with = ",")
156
157    ctx.actions.run(
158        inputs = [input_jar] + classpath_jars,
159        executable = ctx.executable._osgi_wrapper_exe,
160        arguments = [args],
161        outputs = [output_jar],
162        progress_message = "Generating OSGi bundle Manifest for %s" % input_jar.path,
163    )
164
165def _osgi_jar_impl(ctx):
166    if len(ctx.attr.target[JavaInfo].java_outputs) != 1:
167        fail("osgi_jar rule can only be used on a single java target.")
168    target_java_output = ctx.attr.target[JavaInfo].java_outputs[0]
169
170    # source_jars may be a list or a Depset due to:
171    # https://github.com/bazelbuild/bazel/issues/18966
172    source_jars = target_java_output.source_jars
173    if hasattr(source_jars, "to_list"):
174        source_jars = source_jars.to_list()
175    if len(source_jars) > 1:
176        fail("osgi_jar rule doesn't know how to deal with more than one source jar.")
177    source_jar = source_jars[0]
178
179    output_jar = ctx.outputs.output_jar
180
181    input_jar = target_java_output.class_jar
182    classpath_jars = ctx.attr.target[JavaInfo].compilation_info.compilation_classpath.to_list()
183
184    _run_osgi_wrapper(ctx, input_jar, classpath_jars, output_jar)
185
186    return [
187        DefaultInfo(
188            files = depset([output_jar]),
189            # Workaround for https://github.com/bazelbuild/bazel/issues/15043
190            # Bazel's native rule such as sh_test do not pick up 'files' in
191            # DefaultInfo for a target in 'data'.
192            data_runfiles = ctx.runfiles([output_jar]),
193        ),
194        JavaInfo(
195            output_jar = output_jar,
196
197            # compile_jar should be an ijar, but using an ijar results in
198            # missing protobuf import version.
199            compile_jar = output_jar,
200            source_jar = source_jar,
201            compile_jdeps = target_java_output.compile_jdeps,
202            generated_class_jar = target_java_output.generated_class_jar,
203            generated_source_jar = target_java_output.generated_source_jar,
204            native_headers_jar = target_java_output.native_headers_jar,
205            manifest_proto = target_java_output.manifest_proto,
206            neverlink = ctx.attr.neverlink,
207            deps = [dep[JavaInfo] for dep in ctx.attr.deps],
208            runtime_deps = [dep[JavaInfo] for dep in ctx.attr.runtime_deps],
209            exports = [exp[JavaInfo] for exp in ctx.attr.exports],
210            exported_plugins = ctx.attr.exported_plugins,
211            jdeps = target_java_output.jdeps,
212        ),
213    ]
214
215_osgi_jar = rule(
216    implementation = _osgi_jar_impl,
217    outputs = {
218        "output_jar": "lib%{name}.jar",
219    },
220    attrs = {
221        "automatic_module_name": attr.string(),
222        "bundle_copyright": attr.string(),
223        "bundle_description": attr.string(),
224        "bundle_doc_url": attr.string(),
225        "bundle_license": attr.string(),
226        "bundle_name": attr.string(),
227        "bundle_version": attr.string(),
228        "bundle_symbolic_name": attr.string(),
229        "export_package": attr.string_list(),
230        "import_package": attr.string_list(),
231        "target": attr.label(),
232        "deps": attr.label_list(),
233        "runtime_deps": attr.label_list(),
234        "exports": attr.label_list(),
235        "neverlink": attr.bool(),
236        "exported_plugins": attr.label_list(),
237        "_osgi_wrapper_exe": attr.label(
238            executable = True,
239            cfg = "exec",
240            allow_files = True,
241            default = Label("//java/osgi:osgi_wrapper"),
242        ),
243    },
244)
245