-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
kotlin_rules and Ijars: Update the Ijar tooling to do the right thing. #4549
Comments
@iirina Can you take a look at this? |
Can you explain why the automatic ijar generation in the java rules is problematic? Is it because you are using |
I misrepresented the sometimes part. Most Kotlin libraries that a JVM project would use as an external dependency will have DSLs that make heavy use of
If the Kotlin ABI elements were retained by the Ijar tool transparently then special handling would not be needed so Kotlin libraries could be imported by Current dependency management strategies (pubref/rules_maven, bazel-dep, migration-tooling, this one) go beyond this. They generate BUILD file entries which range from from simply listing Current and future versions of these tools could work for Kotlin transparently as long as the ijar tool doesn't strip off the Kotlin ABI elements. This benefits of this feature go beyond just the motivational example I gave above. Some other reasons for why updating the ijar tool:
|
Is there a reference for specifically how these ABI elements are represented in bytecode? |
Just had a closer look. For marker files there is a After some experimentation inlining metadata seems to be embedded in the Metadata annotation. Every class has a Metadata annotation. Using the bytecode tool included in the Intellij Kotlin plugin to inspect the following test file: package test
fun monkey(x: () -> Unit) {
x()
}
inline fun donkey(y: () -> Unit): Int {
y()
return 1
}
fun lonkey(z: () -> Unit): Int {
z()
} d2 entry in metadata -- notice the inlined function includes the param type.
full output
I think I read somewhere that the bytecode contained within the stub of an inlined function is used as well. references: |
ijar will remove non-class files including It does not remove annotations, so For inline methods, I assume the implementation needs to be visible across compilation boundaries? It might need a way to identify inline methods and to include their implementations in the interface jar. |
Yes I think thats right, the compiler seems to generate bytecode mimicking a regular JDK8 higher order function, the lambda invocation instructions must be used as the placeholders, nice design. It seems like a simple enough change, visit classes with Metadata annotations, discover public and protected inline methods and preclude them from bytecode dropping. I could gather a list of the file patterns relevant to the various module types -- as globs ?.
|
In my uninformed opinion, I think it's fine to special-case Kotlin stuff in ijar a little bit; the above "don't drop bytecode from methods that are marked as inline" counts as such in my book. I don't understand the latter half of @hsyed 's comment, though -- how is this new proposed provider relevant to changes to ijar? |
@iberki I've removed the second part from my previous comment. It's not relevant to this issue at all. |
@hsyed -- excellent, I was afraid for a second that my understanding is that hazy :) |
@davidstanke I haven't looked at C++ in a long time and I just opened the JVM bytecode spec last week. I think it would be best implemented by someone who has already worked on the tool. There are two parts to this feature:
1 is quite straight forward and I could implement this, but if someone else does 2 might as well get 1 one done in that PR. |
How would we test the changes ? I can add a test case to rules_kotlin that tests if the inlined methods are retained via a |
That's one option. You could also add a pre-compiled Kotlin .jar somewhere under
and add a test that checks what happens to the class files. Or both. Both is probably the best. |
I've added some test cases, these cover the inlining change and retention of the metadata. Now onto the test. I can add a function to
@lberki why do you think ? |
I'd rather live without (3) because that would make the tests of ijar dependent on Kotlin. Other than that, awesome. |
rules_kotlin will be a part of the CI runs soon. It should catch kotlinc's opinion on the processed ijar. For 3 we could assert function bodies still exist using
asserting this fragment should be enough of a test:
|
Yep, checking the output of |
The information in metadata about which methods are inlined appears to be a serialized proto: https://github.com/JetBrains/kotlin/blob/ee50aec7342ab375a105bb318e6501135e859028/core/deserialization/src/descriptors.proto. @lberki are we OK with added a dependency on proto to ijar? I think there were some concerns about making the c++ singlejar depend on proto, but I'm not sure where that landed. |
I've wired up the tests in the pr (#4632), the ijar step is currently just cp'ing so it can be merged in. |
@hsyed : I assume you mean "the ijar step is currently just cp'ing so it cannot be merged in", right? @cushon : unfortunately, I can't recall or find that discussion anywhere :( I think in Bazel, that is okay because |
Ugh, I was just made aware that the extra dependencies required by protocol buffers make the above solution impractical :( I can think of a few options:
I'd prefer the former one, because then people who don't use Kotlin don't pay the price to support it. Any more clever ideas? |
Normally, bazel uses `ijar` to prepare a jar containing just ABI-entries for classfiles. These are stable when non-API breaking changes are made, and so allow bazel to compile code faster, and are known as "compile jars" Because of bazelbuild/bazel#4549 `rules_jvm_external` doesn't use `ijar`, and instead uses the downloaded jar as the "compile jar". Normally, this is fine, but Bazel 4.0.0 now sets `-Xlint:path` for javac invocations, and this means that if there's a `Class-Path` manifest entry in a compile jar, the jars within _that_ will be checked. This can lead to noisy builds: bazelbuild/bazel#12968 This change generates a "pseudo-compile jar" for `jvm_import` targets. All it does is repack the input jar, excluding the `META-INF/MANIFEST.MF` file. This means that we should avoid compilation problems whilst still working well with Kotlin projects.
Normally, bazel uses `ijar` to prepare a jar containing just ABI-entries for classfiles. These are stable when non-API breaking changes are made, and so allow bazel to compile code faster, and are known as "compile jars" Because of bazelbuild/bazel#4549 `rules_jvm_external` doesn't use `ijar`, and instead uses the downloaded jar as the "compile jar". Normally, this is fine, but Bazel 4.0.0 now sets `-Xlint:path` for javac invocations, and this means that if there's a `Class-Path` manifest entry in a compile jar, the jars within _that_ will be checked. This can lead to noisy builds: bazelbuild/bazel#12968 This change generates a "pseudo-compile jar" for `jvm_import` targets. All it does is repack the input jar, excluding the `META-INF/MANIFEST.MF` file. This means that we should avoid compilation problems whilst still working well with Kotlin projects.
Normally, bazel uses `ijar` to prepare a jar containing just ABI-entries for classfiles. These are stable when non-API breaking changes are made, and so allow bazel to compile code faster, and are known as "compile jars" Because of bazelbuild/bazel#4549 `rules_jvm_external` doesn't use `ijar`, and instead uses the downloaded jar as the "compile jar". Normally, this is fine, but Bazel 4.0.0 now sets `-Xlint:path` for javac invocations, and this means that if there's a `Class-Path` manifest entry in a compile jar, the jars within _that_ will be checked. This can lead to noisy builds: bazelbuild/bazel#12968 This change generates a "pseudo-compile jar" for `jvm_import` targets. All it does is repack the input jar, excluding the `META-INF/MANIFEST.MF` file. This means that we should avoid compilation problems whilst still working well with Kotlin projects.
Normally, bazel uses `ijar` to prepare a jar containing just ABI-entries for classfiles. These are stable when non-API breaking changes are made, and so allow bazel to compile code faster, and are known as "compile jars" Because of bazelbuild/bazel#4549 `rules_jvm_external` doesn't use `ijar`, and instead uses the downloaded jar as the "compile jar". Normally, this is fine, but Bazel 4.0.0 now sets `-Xlint:path` for javac invocations, and this means that if there's a `Class-Path` manifest entry in a compile jar, the jars within _that_ will be checked. This can lead to noisy builds: bazelbuild/bazel#12968 This change generates a "pseudo-compile jar" for `jvm_import` targets. All it does is repack the input jar, excluding the `META-INF/MANIFEST.MF` file. This means that we should avoid compilation problems whilst still working well with Kotlin projects.
Background: JAR files can bundle ProGuard specs under `META-INF/proguard/` [See https://developer.android.com/studio/build/shrink-code] Problem: Bazel previously erroniously ignored these ProGuard specs, leading to failures with, for example, androidx.annotation.Keep. Bad times. There was previously a parallel issue with aar_import. [Fixed in bazelbuild#12749] Solution: This change causes the previously ignored, embedded proguards to be extracted, validated, and then bubbled up correctly via the ProguardSpecProvider. There's also a minor fix to aar_import, adding proguard validation and slightly simplifying the resulting code. For reasoning behind why library proguards should be validated, see the module docstring of proguard_whitelister.py Remaining issues: JAR files brought down from Maven via rules_jvm_external bypass java_import in favor of rolling their own jvm_import, since java_import apparently been broken for Kotlin for years. That'll need a subsequent fix, since this only fixes java_import. For context on the Kotlin breakage, see bazelbuild#4549. For the status on fixes in rules_jvm_external, see bazel-contrib/rules_jvm_external#672
Background: JAR files can bundle ProGuard specs under `META-INF/proguard/` [See https://developer.android.com/studio/build/shrink-code] Problem: Bazel previously erroniously ignored these ProGuard specs, leading to failures with, for example, androidx.annotation.Keep. Bad times. There was previously a parallel issue with aar_import. [Fixed in bazelbuild#12749] Solution: This change causes the previously ignored, embedded proguards to be extracted, validated, and then bubbled up correctly via the ProguardSpecProvider. There's also a minor fix to aar_import, adding proguard validation and slightly simplifying the resulting code. For reasoning behind why library proguards should be validated, see the module docstring of proguard_whitelister.py Remaining issues: JAR files brought down from Maven via rules_jvm_external bypass java_import in favor of rolling their own jvm_import, since java_import apparently been broken for Kotlin for years. That'll need a subsequent fix, since this only fixes java_import. For context on the Kotlin breakage, see bazelbuild#4549. For the status on fixes in rules_jvm_external, see bazel-contrib/rules_jvm_external#672
Problem: java_import has been unusably broken for years for JARs with Kotlin interfaces, since ijar strips out important information. This has caused multiple dependent projects (includng official Bazel ones) to abandon java_import in favor of rolling their own versions, which themselves contain issues that are getting fixed in java_import. Fragmentation is bad, and fragmentation of bugs and fixes is worse. For more, see https://github.com/bazelbuild/rules_jvm_external/blob/master/private/rules/jvm_import.bzl bazelbuild#4549 bazelbuild#14966 bazel-contrib/rules_jvm_external#672 Temporary solution: Until such time as ijar is fixed for Kotlin, this adds a toggle that enables/disables ijar on java_import. This should unblock use of java_import for libraries that might contain Kotlin, so implementations can reunify. It also restores java_import to a state where it works correctly by default. Per the user manual, ijars are a build performance optimization to allow caching of actions that use JARs whose implementations change frequenly. Imported (externally compiled) JARs shouldn't be changing very often, meaning that the build performance cost of disabling ijars for these prebuilt JARs should be relatively low. Therefore, the ijar toggle is off by default, so that the build is correct by default. But ijar is still available though the toggle, just in case someone is importing a Java-interface-only JAR that they change all the time.
Problem: java_import has been unusably broken for years for JARs with Kotlin interfaces, since ijar strips out important information. This has caused multiple dependent projects (includng official Bazel ones) to abandon java_import in favor of rolling their own versions, which themselves contain issues that are getting fixed in java_import. Fragmentation is bad, and fragmentation of bugs and fixes is worse. For more, see https://github.com/bazelbuild/rules_jvm_external/blob/master/private/rules/jvm_import.bzl bazelbuild#4549 bazelbuild#14966 bazel-contrib/rules_jvm_external#672 Temporary solution: Until such time as ijar is fixed for Kotlin, this adds a toggle that enables/disables ijar on java_import. This should unblock use of java_import for libraries that might contain Kotlin, so implementations can reunify. It also restores java_import to a state where it works correctly by default. Per the user manual, ijars are a build performance optimization to allow caching of actions that use JARs whose implementations change frequenly [1]. Imported (externally compiled) JARs shouldn't be changing very often, meaning that the build performance cost of disabling ijars for these prebuilt JARs should be relatively low. Therefore, the ijar toggle is off by default, so the build is correct by default. But ijar is still made available though the toggle, just in case someone is importing a Java-interface-only JAR that they change all the time. [1] https://docs.bazel.build/versions/main/user-manual.html#flag--use_ijars
Hey, all! Figured I'd toss up a quick PR that disables ijar in java_import by default. The goal is to unblock its usage for Kotlin JARs enable reunification around java_import if you folks wanted it. So please see #14967. Would love it if you'd weigh in, lmk what you think, or add to it! Backstory: |
Problem: java_import has been unusably broken for years for JARs with Kotlin interfaces, since ijar strips out important information. This has caused multiple dependent projects (includng official Bazel ones) to abandon java_import in favor of rolling their own versions, which themselves contain issues that are getting fixed in java_import. Fragmentation is bad, and fragmentation of bugs and fixes is worse. For more, see https://github.com/bazelbuild/rules_jvm_external/blob/master/private/rules/jvm_import.bzl bazelbuild#4549 bazelbuild#14966 bazel-contrib/rules_jvm_external#672 Temporary solution: Until such time as ijar is fixed for Kotlin, this adds a toggle that enables/disables ijar on java_import. This should unblock use of java_import for libraries that might contain Kotlin, so implementations can reunify. It also restores java_import to a state where it works correctly by default. Per the user manual, ijars are a build performance optimization to allow caching of actions that use JARs whose implementations change frequenly [1]. Imported (externally compiled) JARs shouldn't be changing very often, meaning that the build performance cost of disabling ijars for these prebuilt JARs should be relatively low. Therefore, the ijar toggle is off by default, so the build is correct by default. But ijar is still made available though the toggle, just in case someone is importing a Java-interface-only JAR that they change all the time. [1] https://docs.bazel.build/versions/main/user-manual.html#flag--use_ijars
For (future) readers of this issue: Plenty of additional discussion over in #14967. To answer a dangling question above: It doesn't look like they bothered with ijar in aar_import. |
To recap some of the reason ijar exists for |
To expand on the previous comment, the option that @kevin1e100 brought up in #14967 (comment) of having ijar not strip classes annotated with We might also consider adding more validation to |
@cushon and @kevin1e100, a question coming from a place of true ignorance about the state of a different alternative: I'd read @kevin1e100's comment above (#4549 (comment)) about generating ABI jars (equivalent to ijars?) via an official kotlinc plugin. I'd also seen that Kevin had contributed a fix to the other blocking issue he'd linked (awesome). Do we know the status of that, or if it is a feasible alternative? That is, would it also work for Java and therefore be an ijar alternative that would work with Kotlin? (I got as far as seeing that there was maybe some experimental ABI jar support in rules_kotlin, maybe based on it.) |
Problem: java_import has been unusably broken for years for JARs with Kotlin interfaces, since ijar strips out important information. This has caused multiple dependent projects (including official Bazel ones) to abandon java_import in favor of rolling their own versions, which themselves contain issues that are getting fixed in java_import. Fragmentation is bad, and fragmentation of bugs and fixes is worse. For more, see https://github.com/bazelbuild/rules_jvm_external/blob/master/private/rules/jvm_import.bzl bazelbuild#4549 bazelbuild#14966 bazel-contrib/rules_jvm_external#672 Temporary solution: Until such time as ijar is fixed for Kotlin, this adds a toggle that optionally disables ijar on java_import. This should unblock use of java_import for libraries that might contain Kotlin and allow implementations to reunify.
@cpsauer the jvm-abi-gen plugin I'm guessing you're asking about produces ijar-like artifacts when compiling Kotlin sources. I don't believe isn't usable with Jar files the Kotlin compiler previously produced, so it's not helpful in the context of |
Oh! Got it. Bummer Thanks, @kevin1e100, for taking the time to reply. |
I had a quick look at kevin1e100's That will meant that ijar / There's some possible future work around around more precise interface jars for Kotlin. To try to summarize, theoretically ijar could decode the proto in the
If someone wants to take a swing at that I think we'd want to try to accept that contribution, but it could be a bunch of work. |
Remove unused handling of the `KeepForCompile` attribute, and instead check for the `@kotlin.Metadata` annotation and preserve all Kotlin classes. #4549 RELNOTES: Make ijar / java_import preserve classes with `@kotlin.Metadata` annotations PiperOrigin-RevId: 441243993
There's more that could be done here (see the note in #4549 (comment) about future work), but ijar / |
That's amazing, thank you so much! |
Wow! That's awesome. Thanks @cushon for getting it done. |
Thanks for making that happen @cushon! |
Remove unused handling of the `KeepForCompile` attribute, and instead check for the `@kotlin.Metadata` annotation and preserve all Kotlin classes. bazelbuild#4549 RELNOTES: Make ijar / java_import preserve classes with `@kotlin.Metadata` annotations PiperOrigin-RevId: 441243993
I have been working on a new set of kotlin rules and they are currently being migrated to bazelbuild/rules_kotlin.
One goal with the design of the Kotlin JVM ruleset is to stay as close to the native java rules and infrastructure as possible. The automatic Ijars generation is problematic as the java rules auto create ijars. Ijars generated for Kotlin work in a lot of cases, but inlined methods break (might be other cases).
Proposal: Adapt Ijar to do the right thing for Kotlin Jars.
I propose making a change to the behaviour of the ijar tool so that it does the right thing when it comes to Kotlin metadata. Adding this change to Bazel core simplifies the Kotlin rules and allows more reuse out of java common infrastructure. I propose the ijar tool be updated to:
Motivating example 1: dep management tooling /
java_import
.I have just added a
kotlin_import
rule to:java_import
, andKotlinInfo
provider so thatfull_compile_jars
are always selected when compiling Kotlin.Any fancy java dependency management tooling that we develop will likely work via orchestrations of
java_import
,java_library
andjava_import_external
it's a shame to have to add a layer for Kotlin when it's barely needed.The text was updated successfully, but these errors were encountered: