Skip to content

Commit a535387

Browse files
committed
Propagate CcInfo for native dependencies
This will, in a follow-up, allow us to create and link `java_{binary,test}#laucher` as the docs suggest. Unfortunately, `JavaInfo#transitive_native_libraries` fails to propagate sufficient information about transitive dependencies of the native libraries, so linking fails because of missing symbols from transitive libraries that should be there.
1 parent 767e441 commit a535387

File tree

7 files changed

+149
-2
lines changed

7 files changed

+149
-2
lines changed

java/bazel/rules/bazel_java_library.bzl

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
Definition of java_library rule.
1717
"""
1818

19+
load("@rules_cc//cc:find_cc_toolchain.bzl", "use_cc_toolchain")
1920
load("//java/common:java_semantics.bzl", "semantics")
2021
load("//java/common/rules:android_lint.bzl", "android_lint_subrule")
2122
load("//java/common/rules:java_library.bzl", "JAVA_LIBRARY_ATTRS")
@@ -60,6 +61,6 @@ java_library = rule(
6061
"sourcejar": "lib%{name}-src.jar",
6162
},
6263
fragments = ["java", "cpp"],
63-
toolchains = [semantics.JAVA_TOOLCHAIN],
64+
toolchains = [semantics.JAVA_TOOLCHAIN] + use_cc_toolchain(mandatory = False),
6465
subrules = [android_lint_subrule],
6566
)

java/common/rules/impl/basic_java_library_impl.bzl

+100-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
Common code for reuse across java_* rules
1717
"""
1818

19+
load("@rules_cc//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_TYPE", "find_cc_toolchain")
20+
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
1921
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
22+
load("//java/common:java_semantics.bzl", "semantics")
2023
load("//java/common/rules:android_lint.bzl", "android_lint_subrule")
2124
load("//java/private:boot_class_path_info.bzl", "BootClassPathInfo")
2225
load("//java/private:java_common_internal.bzl", "target_kind")
@@ -124,6 +127,7 @@ def basic_java_library(
124127
resources = list(resources)
125128
resources.extend(properties)
126129

130+
native_libraries = _collect_native_libraries(deps, runtime_deps, exports)
127131
java_info, compilation_info = compile_action(
128132
ctx,
129133
ctx.outputs.classjar,
@@ -138,7 +142,7 @@ def basic_java_library(
138142
resources,
139143
resource_jars,
140144
classpath_resources,
141-
_collect_native_libraries(deps, runtime_deps, exports),
145+
native_libraries,
142146
javacopts,
143147
neverlink,
144148
ctx.fragments.java.strict_java_deps,
@@ -150,6 +154,25 @@ def basic_java_library(
150154
)
151155
target = {"JavaInfo": java_info}
152156

157+
if native_libraries:
158+
native_header_cc_info = _make_native_header_cc_info(ctx, java_info)
159+
dependencies_cc_info = cc_common.merge_cc_infos(cc_infos = native_libraries)
160+
161+
target["CcInfo"] = cc_common.merge_cc_infos(
162+
cc_infos = [
163+
native_header_cc_info,
164+
# Native dependencies have the same semantics as
165+
# `cc_library#implementation_deps`. We only want to propagate
166+
# `Cc{Debug,Linking}Context` to libraries depending on this.
167+
CcInfo(
168+
linking_context = dependencies_cc_info.linking_context,
169+
debug_context = dependencies_cc_info.debug_context(),
170+
),
171+
],
172+
)
173+
else:
174+
target["CcInfo"] = _make_native_header_cc_info(ctx, java_info)
175+
153176
output_groups = dict(
154177
compilation_outputs = compilation_info.files_to_build,
155178
_source_jars = java_info.transitive_source_jars,
@@ -234,6 +257,82 @@ def _collect_native_libraries(*attrs):
234257
"""
235258
return _filter_provider(CcInfo, *attrs)
236259

260+
def _make_native_header_cc_info(ctx, java_info):
261+
"""Creates a `CcInfo` for compiling and linking against generated headers.
262+
263+
Use this call to generate a single header from the `-native.jar` and wrap it
264+
in `CcInfo` for consumers.
265+
266+
Args:
267+
ctx: (RuleContext) The rule context of the caller.
268+
java_info: (JavaInfo) The `JavaInfo` provider to extract headers from.
269+
Returns:
270+
(CcInfo): The `CcInfo` provider for compiling and linking against
271+
generated headers.
272+
"""
273+
274+
# 1. Check if there's jar with native headers.
275+
native_headers_jar = java_info.outputs.native_headers
276+
if not native_headers_jar:
277+
# There's no jar with native headers. Simply return an empty provider so
278+
# we have a well-defined API.
279+
return CcInfo()
280+
281+
# 2. Check if there's a `cc_toolchain` available.
282+
if CC_TOOLCHAIN_TYPE in ctx.toolchains:
283+
cc_toolchain = find_cc_toolchain(ctx, mandatory = False)
284+
else:
285+
# Work around a bug in `find_cc_toolchain()` which requires a rule to
286+
# declare an (optional) dependency on the Cc toolchain even if
287+
# `mandatory = False`.
288+
cc_toolchain = None
289+
if not cc_toolchain:
290+
# No `cc_toolchain` available, so there's no way for users to build
291+
# native dependencies. Simply return an empty provider so we have a
292+
# well-defined API.
293+
return CcInfo()
294+
295+
# 3. Get tool to generate header from jar.
296+
java_toolchain = semantics.find_java_toolchain(ctx)
297+
if not java_toolchain.native_header_generator:
298+
# We don't have a tool to generate the combined header. Simply return an
299+
# empty provider so we have a well-defined API.
300+
return CcInfo()
301+
302+
args = ctx.actions.args()
303+
args.add("--jar", native_headers_jar)
304+
305+
native_header = ctx.actions.declare_file("{}.native.h".format(ctx.attr.name))
306+
args.add("--output", native_header)
307+
308+
ctx.actions.run(
309+
executable = java_toolchain.native_header_generator,
310+
outputs = [
311+
native_header,
312+
],
313+
inputs = [
314+
native_headers_jar,
315+
],
316+
mnemonic = "JavaNativeHeader",
317+
progress_message = "Generating native header for %{label}",
318+
toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
319+
arguments = [
320+
args,
321+
],
322+
)
323+
324+
# TODO(yannic): Add `CcCompilationContext` for `jni.h`.
325+
compilation_context = cc_common.create_compilation_context(
326+
headers = depset(
327+
direct = [
328+
native_header,
329+
],
330+
),
331+
)
332+
return CcInfo(
333+
compilation_context = compilation_context,
334+
)
335+
237336
def construct_defaultinfo(ctx, files_to_build, files, neverlink, *extra_attrs):
238337
"""Constructs DefaultInfo for Java library like rule.
239338

java/common/rules/java_toolchain.bzl

+11
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ JavaToolchainInfo, _new_javatoolchaininfo = provider(
3838
fields = {
3939
"bootclasspath": "(depset[File]) The Java target bootclasspath entries. Corresponds to javac's -bootclasspath flag.",
4040
"ijar": "(FilesToRunProvider) The ijar executable.",
41+
"native_header_generator": "(FilesToRunProvider) (optional) The executable to generate a combined native header file.",
4142
"jacocorunner": "(FilesToRunProvider) The jacocorunner used by the toolchain.",
4243
"java_runtime": "(JavaRuntimeInfo) The java runtime information.",
4344
"jvm_opt": "(depset[str]) The default options for the JVM running the java compiler and associated tools.",
@@ -97,6 +98,7 @@ def _java_toolchain_impl(ctx):
9798
java_toolchain_info = _new_javatoolchaininfo(
9899
bootclasspath = bootclasspath_info.bootclasspath,
99100
ijar = ctx.attr.ijar.files_to_run if ctx.attr.ijar else None,
101+
native_header_generator = ctx.attr.native_header_generator.files_to_run if ctx.attr.native_header_generator else None,
100102
jacocorunner = ctx.attr.jacocorunner.files_to_run if ctx.attr.jacocorunner else None,
101103
java_runtime = java_runtime,
102104
jvm_opt = depset(get_internal_java_common().expand_java_opts(ctx, "jvm_opts", tokenize = False, exec_paths = True)),
@@ -390,6 +392,15 @@ include any API-generating annotation processors.
390392
Label of the ijar executable.
391393
""",
392394
),
395+
"native_header_generator": attr.label(
396+
cfg = "exec",
397+
allow_files = True,
398+
executable = True,
399+
doc = """
400+
Label of the executable to generate a native header from a jar of native
401+
headers.
402+
""",
403+
),
393404
"jacocorunner": attr.label(
394405
cfg = "exec",
395406
allow_single_file = True,

test/repo/BUILD.bazel

+23
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
1+
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
12
load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_test") # copybara-use-repo-external-label
23
load("@rules_java//toolchains:default_java_toolchain.bzl", "default_java_toolchain") # copybara-use-repo-external-label
34

5+
cc_library(
6+
name = "native_dep",
7+
srcs = [
8+
"src/native_dep.cc",
9+
],
10+
)
11+
412
java_library(
513
name = "lib",
614
srcs = ["src/Main.java"],
15+
deps = [
16+
":native_dep",
17+
],
18+
)
19+
20+
cc_test(
21+
name = "native_test",
22+
srcs = [
23+
"src/native_test.cc",
24+
],
25+
deps = [
26+
":lib",
27+
"@googletest//:gtest",
28+
"@googletest//:gtest_main",
29+
],
730
)
831

932
java_binary(

test/repo/MODULE.bazel

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module(name = "integration_test_repo")
22

3+
bazel_dep(name = "googletest", version = "1.15.2")
4+
bazel_dep(name = "rules_cc", version = "0.0.15")
35
bazel_dep(name = "rules_java", version = "7.5.0")
46
archive_override(
57
module_name = "rules_java",

test/repo/src/native_dep.cc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int my_number() {
2+
return 42;
3+
}

test/repo/src/native_test.cc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "gmock/gmock.h"
2+
#include "gtest/gtest.h"
3+
4+
int my_number();
5+
6+
TEST(MyNativeTest, MyNumberReturnsExpectedValue) {
7+
EXPECT_THAT(my_number(), testing::Eq(42));
8+
}

0 commit comments

Comments
 (0)