Status Update
Comments
sg...@google.com <sg...@google.com> #2
We should consider revisiting this, as libraries start to depend on classes targeting higher Java versions. One example is Moshi, which has a META-INF/versions/16/...
.
This can always be supported by users of the R8 API by implementing a custom ClassFileResourceProvider
. If the R8 ArchiveClassFileProvider
should handle this it would have to be extended with a JDK version for which to include the relevant class files in META-INF/versions/...
and ignore duplicate classes outside META-INF/versions/...
and for previous versions in META-INF/versions/...
.
Similar for the CLI supporting this requires JDK version.
The Moshi 1.14.0 JAR is javap
to disassemble files under META-INF/versions
use this:
javap -v jar:file:/usr/local/google/home/sgjesse/Downloads/moshi-1.14.0.jar\!/META-INF/versions/16/com/squareup/moshi/RecordJsonAdapter.class
cp...@google.com <cp...@google.com> #3
Hi, I don't have any request at the moment, but I was looking into Android's treatment of mrjars as part of migrating Guava off Unsafe
, so I wanted to summarize my thoughts.
In short: The current Android behavior might be convenient for us, and it seems hard to me for Android to do better. But I'm not sure I'll want to make Guava rely on the current behavior.
So: I went looking into the history here (META-INF
The summary in
So what makes things interesting is the scenario in
And then the complexity comes from the obvious things:
- APIs and language features from a given release like "Java 11" do not necessarily all become available at a single Android release.
- The availability of some APIs or language features may depend on whether users have opted in to "default" library desugaring.
Per
- Allow the app developer to specify a minimum "Java version," and put the files for that Java version into the dex. This doesn't really "solve" either problem, but it allows developers who really want (e.g.) Moshi's record support to get itāwhile taking the risk that they might enable some other feature that's available under Java 16 but wouldn't work under the Android runtimes that they target.
- Implement a concept of "multi-release dex files." This would help with problem #1 above but not problem #2, so library developers would have to be conservative in what they make available at a given release. Or perhaps this option could help with problem #2 in some sense:
- If a library or app specifies that it does require default library desugaring, then it can choose what to make available under a given release under that assumption.
- Or the hypothetical multi-release-dex-file format could provide separate directories for "SDK 21 with default desugaring" and "SDK 21 with minimal desugaring." But that leads to complexity: Does "SDK 22 with default desugaring" inherit from "SDK 22 with minimal desugaring" or "SDK 21 with default desugaring?" (And there's
nio
desugaring, too.)
(Or maybe there's a way for the toolchain to detect, based on everything it knows, whether a given version is likely to actually work? But that feels dangerous, since a given version could use, say, reflection, which would hide its usages of APIs from the toolchain.)
Anyway, the reason that I'm looking into this is Guava's Android flavor: We really want it to sun.misc.Unsafe
when it's used under the JVMVarHandle
first. But VarHandle
never seems to work under Android at the moment, at least in our internal repo. (See CL 711733182.) Still, maybe we can make the Android flavor try to use VarHandle
first as long as it catches all the problems that might arise under Android (assuming that I can identify all those problems)? Or maybe I can just perform a runtime check on java.runtime.name
, skipping VarHandle
under Android? (I don't think that merely checking for the presence of VarHandle
at runtime is enough, given some of the errors that I saw.) There would be some runtime cost to either approach, but I'd guess it's not prohibitive.
But then I had gotten to wondering whether we could use a multi-release jar for this: If Android continues to ignore everything under META-INF
, then I can put the "real" VarHandle
code under META-INF/versions/9
, and I can put a stub under the root. This would be sufficient today, but it would stop being sufficient if Android were to someday include the META-INF/versions/9
version of the code and then try to use it at runtime in a situation in which it's not supported. (A multi-release jar would also require nonzero effort to configure our internal and external builds for.)
So my guess is that I'll try to handle this in some way other than with a multi-release jar. More generally, we've been using reflection to test for specific classes, rather than using a multi-release jar. I think this has been partially to avoid the one-time cost of the mrjar build setup (and, until recently, sometimes as a way to avoid configuring our Maven build to use a newer version of javac... :)). But I think we have also viewed it as a way to serve Android. So we'd typically put a "real" implementation of something like Moshi's record adapter under the root of the jar, but then we'd attempt to use it only if we detect that java.lang.Record
is available at runtime. (I don't know for sure how well that works with library desugaring, but it at least helps with "Is java.lang.Record
itself available?")
Description
The ArchiveClassFileProvider currently ignores everything under META-INF, so a multi-release JAR file should work fine, as all class files under META-INF/versions must have a corresponding class file outside META-INF/versions/X.
The question is if there should be an option to set the version, so that the files under META-INF/versions/X can override the default ones.