Skip to content

Propagate CcInfo for native dependencies #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Yannic
Copy link
Contributor

@Yannic Yannic commented Jan 2, 2025

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.

@Yannic Yannic force-pushed the java_library_cc_info branch 4 times, most recently from 4901d3f to a535387 Compare January 2, 2025 13:34
args.add("--output", native_header)

ctx.actions.run(
executable = java_toolchain.native_header_generator,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative could be to generate a tree artifact of headers as in https://github.com/fmeum/rules_jni/blob/e93011edbf0dcd48a00bd3147081b9c435ceda9b/jni/private/jni_headers.bzl#L20, which avoids a custom tool and may work better with .d pruning or include scanning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure yet what's the best approach here TBH.

  • having a single header file per java_binary seems nice, but requires tooling to merge the headers from the jar file
  • having one header per class is also not too bad, but also requires a dedicated tool (e.g., @bazel_tools//tools/zip:zipper like you used), could cause issues with duplicate headers if builds are not one-version clean (or because of META-INF from the -native.jar), and may add one -I per Java target for the tree artifact, which makes header lookup much more expensive
  • not doing anything with the header jar at all and keep support for native dependencies minimal in rules_java and rely on something like rules_jni to provide jni_{cc,rust,...}_library

My main interest here is to get launchers working in Bazel, so I'll probably go with the third option and remove the generated native header.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I'm not sure myself and definitely didn't want to suggest the approach of rules_jni is better overall. Keeping this out of rules_java until we find a great solution is probably the easiest way to achieve your goal.

Sidenote: In rules_jni, I also faced the problem that JNI headers and their native implementation tend to create cyclic dependencies: the native implementation needs to depend on the Java target for the headers, but the Java target needs to depend on the implementation to have it linked in. I "solved" this by adding implicit targets via a macro. What's your plan for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sidenote: In rules_jni, I also faced the problem that JNI headers and their native implementation tend to create cyclic dependencies: the native implementation needs to depend on the Java target for the headers, but the Java target needs to depend on the implementation to have it linked in. I "solved" this by adding implicit targets via a macro. What's your plan for this?

I don't think there's a cyclic dependency at build time. The Java part will compile just fine, and so does the native code (% possible the generated native header, but we can get that from JavaInfo). There is, however, a runtime dependency cycle of course.

I think we need 2 modes here:

  • "shell launcher" mode, where we need to link a .so with all native deps and System.loadLibrary() it somehow, and
  • "custom native launcher" mode, where the user sets --java_launcher (or java_{binary,test}#launcher) to cc_library (or something equivalent that exports CcInfo) and java_{binary,test} links the launcher and all native dependencies into a single executable and concatenates the _deploy.jar to the binary (i.e., what java_binary#launcher documents, % accepting cc_library instead of relying on CcLauncherInfo).

For the former, I think we can generate a wrapper main class that calls System.loadLibrary() before calling the "real" main function. I think that's somehow easier in rules_java as we control java_{binary,test} and can do whatever is needed. For the latter, it's up to the user to provide a native main() that starts the JVM (which then automatically has all native methods registered IIUC). We should provide a library to make that easier for users here, but it's not strictly required as one could roll their own.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a working prototype locally, but it unfortunately modifies JavaInfo and breaks without some further patches because rules_java wraps the Starlark provider with the native provider in some places and the field with CcLinkingContext is lost.

@Yannic Yannic force-pushed the java_library_cc_info branch 2 times, most recently from c0a0a56 to cc14213 Compare January 2, 2025 17:45
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.
@Yannic Yannic force-pushed the java_library_cc_info branch from cc14213 to 79bf77c Compare January 2, 2025 17:50
@Yannic Yannic marked this pull request as ready for review January 2, 2025 17:52
@Yannic Yannic requested review from comius and a team as code owners January 2, 2025 17:52
@Yannic Yannic requested a review from fmeum January 2, 2025 17:52
debug_context = dependencies_cc_info.debug_context(),
)
else:
target["CcInfo"] = CcInfo()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a common case, you can reduce retained heap by sharing a single empty instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how much of a difference this makes in practice: java_library will output CcInfo, so every target with deps will take the if case and produce a different, non-equal CcInfo with probably no native deps.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I see. I don't know whether CcInfo merging attempts to preserve empty instances, which would help here.

That said, I now realize that I don't quite understand why each java_library now needs to advertise CcInfo. What information is missing from transitive_native_libraries? Why can't this be a private provider distinct from CcInfo up to the java_binary level?

@hvadehra
Copy link
Member

hvadehra commented Jan 8, 2025

FYI, the missing info was deliberately removed. See https://docs.google.com/document/d/10isTEK5W9iCPp4BIyGBrLY5iti3Waaam6EeGVSjq3r8/edit?tab=t.0#heading=h.fs8cvfw0s0um

@comius can hopefully provide more on the rationale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants