google / guava

Google core libraries for Java
Apache License 2.0
49.68k stars 10.8k forks source link

Require Java 11+ to *build* Guava but continue to support Java 8 at runtime #6549

Open cpovirk opened 1 year ago

cpovirk commented 1 year ago

I'd mentioned this in https://github.com/google/guava/issues/3990#issuecomment-1444307178.

It would be nice to be able to refer in source code to newer types (e.g., CRC32C, VarHandle, and @ReservedStackAccess)—with, of course, appropriate runtime guards. Of course, that would be incompatible with one of the possible approaches to #3990, which is to use --release.

(Using Java 11 might also still dodge bugs in older versions of javac.)

We'd probably accept a PR for this unless some of our users want to tell us that they build Guava from source themselves with javac 8 (or 9 or 10), in which case the decision would get more complex.

cpovirk commented 1 year ago

Of course, that would be incompatible with one of the possible approaches to https://github.com/google/guava/issues/3990, which is to use --release.

Well, not completely: We could use --release 8 but then perform a second compilation with (e.g.) --release 11 to put a second set of classes into a multi-release jar. Even though multi-release jars don't do one of the things we might like, I think they could work well here. We could even imagine conditionally using APIs like CRC32C internally by providing an exception-throwing implementation for Java 8 in addition to a "real" implementation for newer versions and then making sure to perform appropriate checks before using the implementation class.

ben-manes commented 1 year ago

@ReservedStackAccess is only available to privileged code during bootstrap and be ignored otherwise, unless -XX:-RestrictReservedStack is specified. Similarly for @Contended and its flag -XX:-RestrictContended. That unfortunately makes it useless for library code.

It may be reasonable to use Atomic*FieldUpdaters instead of Unsafe. When introduced in Java 5 they were known to have performance issues. That may be mostly resolved according to Faster Atomic*FieldUpdaters for Everyone. You might consider running a fresh set of benchmarks on those usages to see if the impact is acceptable.

I would be inclined to instead make jdk11 the minimum version. Those who are performance sensitive should not be on jdk8 and those users are likely to only need critical bug fixes to be backported. Android annoyingly only supports a subset of jdk11, but likely enough for your needs. You'll certainly have people complain but external are suffering regardless and internal are posing a security risk, so you might not have as much actionable push back if pursued.

I recall some tools were not multi-release jar friendly (e.g. if naively creating a fatjar) and since they are not widely used, I'd consider that to likely be a higher risk of unexpectedly breaking someone in a surprising way than adopting jdk11.

cpovirk commented 1 year ago

Thanks, I'm not sure whether I ever knew that about the annotations. I'll have to dig up some past threads and leave a link from them to here.

I'll also read your link about the field updaters. That's good news for someday getting off Unsafe.

As for requiring a new Java version, that is above my pay grade :) Thankfully, even our final straggler internally was moved off Java 8 a while back, but we'll likely need to maintain compatibility as long as enough open-source Google and non-Google libraries do.

Sadly, if we try to make only bug fixes to a Java 8 backport, I'd expect the same kind of version conflicts that users are used to from the days when we often removed APIs: If one of your deps updates to Guava 32.4.0, then it might use APIs that aren't available to you.

It's good to know that we should look into the fat-jar situation if we try mrjars. I think we had some trouble with that kind of thing internally, if I'm remembering right. Hopefully it's been sorted out both internally and externally, but it's worth remembering that there are risks to everything (just in case the recent CVE-Windows experience didn't remind me enough :)). And at the moment, the benefits of using Java 9+ APIs are looking small (especially after what you said about the annotations), so we will have less motivation to go with a multi-release jar (though it's still a possibility).

--

One other note that I should have put in my original post:

Also related to toolchains: #5457 That's where I'd previously mentioned https://github.com/google/error-prone/issues/3895#issuecomment-1542294317, which is the key to testing Java 8 compatibility even when building with a newer version.

cpovirk commented 1 year ago

My guess at the full set of related commits in Error Prone is:

cpovirk commented 1 year ago

We might also eventually see new versions of Maven plugins drop support for Java 8, as noted in https://github.com/uber/NullAway/pull/778.

In fact, even today, we have special build setup for Error Prone, which requires Java 11+ [edit: for now].

cpovirk commented 11 months ago

If any of our annotation dependencies start being built for Java 9+, then we would start to need to build with a newer javac in all cases. I speculate that the annotations targeting Java 9+ might still be OK to references from a Guava that targets Java 8, but we'd want to verify that—especially since Java 8 has a bug in its handling of type-use annotations that it can't resolve.

cpovirk commented 9 months ago

An interesting question from an internal discussion:

I wonder if multi-release annotation processor jars work.

I would like to think so, but that sounds like a good thing for us to check if we ever look seriously into publishing a multi-release jar.

cpovirk commented 9 months ago

(As I just noted in https://github.com/google/guava/issues/3990#issuecomment-1721703548, even a build-time requirement could be inconvenient for users if they build Guava themselves: In this case, it could be inconvenient if they have continued to build with an older JDK version. So, for example, if an organization both builds with JDK 8 and runs with JDK 8, then they'd no longer be able to build Guava if its build required JDK 11, even if newer compilers could produce a version of Guava that runs on JDK 8.)

cpovirk commented 9 months ago

I am in the process of trying to conditionally use a Java 9+ API again as part of https://github.com/google/guava/issues/6634#issuecomment-1736007128 :\

cpovirk commented 8 months ago

On the toolchain front: I heard a while back that JDiff breaks at JDK ~13, so we wouldn't necessarily want to build with newer versions, at least for our Javadoc generation. Or we could finally migrate to japicmp, which seems to be the replacement that everyone recommends.

[edit: But I didn't see any obvious problems when https://github.com/google/guava/pull/7087 changed Javadoc+JDiff to use Java 21. I suppose it's possible that JDiff failed silently. Or maybe what I heard is that JDiff breaks on JDK 13 bytecode or something?]

[edit: Or did it fail after all?]

cpovirk commented 8 months ago

This would also be convenient (but not strictly necessary) if we want to start generating a module-info.class (https://github.com/google/guava/issues/2970): That requires JDK9+ at build time (perhaps configured like this, incidentally). If we don't have JDK9+, then our options are to submit a precompiled module-info.class to the repo or to skip the module-info compilation when run under JDK8 (and then set up our release configuration to require JDK9+ (which in practice means JDK11+, which is fine, since that's what we'd be using, anyway)).

cpovirk commented 8 months ago

I just noticed that we could simplify our build configuration slightly if we could require 17+:

https://github.com/google/guava/blob/280b5d2f606b5f37304d728311cf492d45f95843/pom.xml#L339-L345

That alone is clearly not a reason to do so, but it's a reminder that sometimes upgrades solve problems. And while upgrading to JDK 13 or so would break JDiff, it would also let us use the nicer format for code snippets in our Javadoc. I think that, in particular, we could start lines with "@Override," etc. without having that interpreted as a Javadoc tag.

cpovirk commented 7 months ago

More toolchain thoughts:

cpovirk commented 4 months ago

We're hitting a javac8 issue again in https://github.com/google/guava/pull/7080:

[INFO] --- compiler:3.8.1:testCompile (default-testCompile) @ guava-tests ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 643 source files to /home/runner/work/guava/guava/guava-tests/target/test-classes
[INFO] -------------------------------------------------------------
Warning:  COMPILATION WARNING : 
[INFO] -------------------------------------------------------------
Warning:  /home/runner/work/guava/guava/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java:[34,23] ProviderList is internal proprietary API and may be removed in a future release
Warning:  /home/runner/work/guava/guava/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java:[35,23] Providers is internal proprietary API and may be removed in a future release
Warning:  /home/runner/work/guava/guava/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java:[84,4] ProviderList is internal proprietary API and may be removed in a future release
Warning:  /home/runner/work/guava/guava/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java:[84,29] Providers is internal proprietary API and may be removed in a future release
Warning:  /home/runner/work/guava/guava/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java:[85,30] ProviderList is internal proprietary API and may be removed in a future release
Warning:  /home/runner/work/guava/guava/guava-tests/test/com/google/common/hash/MacHashFunctionTest.java:[85,4] Providers is internal proprietary API and may be removed in a future release
[INFO] 6 warnings 
[INFO] -------------------------------------------------------------
[INFO] -------------------------------------------------------------
Error:  COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
Error:  An exception has occurred in the compiler (1.8.0_402). Please file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com/) after checking the Bug Database (http://bugs.java.com/) for duplicates. Include your program and the following diagnostic in your report. Thank you.
java.lang.AssertionError: annotation tree hasn't been attributed yet: @Nullable()
    at com.sun.tools.javac.util.Assert.error(Assert.java:133)
    at com.sun.tools.javac.util.Assert.checkNonNull(Assert.java:118)
    at com.sun.tools.javac.comp.Check.validateTypeAnnotation(Check.java:2748)
    at com.sun.tools.javac.comp.Attr$TypeAnnotationsValidator.visitAnnotation(Attr.java:4484)
...

(https://bugs.openjdk.org/browse/JDK-8144101)

Also, I think I saw that new versions of Maven will soon require Java 17. That may require us to finally do the toolchain setup if we want to test under Java 8.

cpovirk commented 3 months ago
cpovirk commented 3 months ago

Builds with Java 11 are fine, but if we go to even newer versions (under the theory that we should build with the newest version available), it appears that we'll have problems with Javadoc. Maybe we can continue generating Javadoc snapshots with java 11, even as we perform the main build with a newer version (especially since we target Java 8 and restrict ourselves to Java 8 APIs)? (Or maybe we can, you know, make it work with new versions :) But I fear that the problem is JDiff, so it might not be easy to fix.) We might see problems with that approach if we try to use Java 12+ APIs internally, but I don't know that we'll do that anytime soon. And even if we do, we should be providing Java-8-compatible sources alongside the Java 12+ sources, so I'd hope that we can "just" point Javadoc to those sources.

ben-manes commented 3 months ago

Not sure if it helps, but I was pinged a few years ago by @lvc showing off his tool (example). Looks like an inactive project and jdiff is too, but maybe could be revived easier?

Oh, and mentioned in https://github.com/google/guava/issues/2963

cpovirk commented 3 months ago

Oh, good point, thanks.

I occasionally hear of a new such tool, and I dump it into a list I have in an internal issue about JDiff. It looks like the tool that I've seen the most energy around is japicmp (used by AdoptJDK and recommended by Kotlin).

cpovirk commented 1 month ago

We hit one interesting quirk when building with JDK 21+ and running tests under JDK 8 this week: https://github.com/jspecify/jspecify/pull/517.

That happened specifically with JUnit 5, but it could happen in other code that performs reflection. I haven't tested Guava with the 21-8 mix to see what happens here, so I'm just mentioning the problem now as something that we'd want to remember in case we encounter it when we try toolchains for Guava or our other projects.

(We still won't be able to release under JDK 21+ or even 13+, thanks to JDiff.)

cpovirk commented 3 weeks ago

Another note on toolchains: https://openjdk.org/jeps/471 is coming, so eventually we'll find it difficult to compile against the newest JDK and continue to refer to Unsafe.