-
Notifications
You must be signed in to change notification settings - Fork 268
Add Java Platform Module System (JPMS) support #1601
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
base: master
Are you sure you want to change the base?
Conversation
- Add module-info.java descriptors for core and extension modules - Configure multi-release JAR compilation for Java 9+ compatibility - Maintain Java 8 backward compatibility through multi-release structure - Support for Guava, JUnit, JSpecify, Error Prone, and ASM dependencies - Extension modules: truth-proto-extension, truth-liteproto-extension, truth-re2j-extension
Fixes #605 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I should have thought of this after the Guava modularization landed.
If I may give a suggestion, I find the setup demonstrated by Maven more flexible, i.e., Java 9 by default and one extra compilation with Java 8 excluding the module descriptor to make sure no Java 9 API is used. Plus, it's IDE-friendly because the module descriptor is loaded properly. |
@scordio Good point. I'll give that a try. |
I ended up breaking my teeth trying to make the Javadoc output modular. It's not going to be technically possible until the Protobuf dependency is made modular. I'm trying to move in that direction at protocolbuffers/protobuf#16133 but success is not guaranteed. |
Resolves module system conflicts by moving liteproto extension from shared com.google.common.truth.extensions.proto package to unique com.google.common.truth.extensions.liteproto namespace. This eliminates split package violations that prevented proper module boundaries. Enables aggregated Javadoc generation using legacyMode=true to work around protobuf dependency conflicts between liteproto (protobuf-lite:3.0.1) and proto (protobuf-java:4.32.1) extensions that caused module-path failures.
I pushed a new commit that addresses your comments, and fixed some split packages that I missed before. Please let me know what you think. |
370ee24
to
2659b4f
Compare
It looks like I botched up the latest commit. Update coming soon. |
- Separate CI build (JDK 11) from test phases (JDK 8/11/17) - Centralize module-info compilation config in parent POM - Use two-stage compilation: module-info with release=9, base code with Java 8 - Maintain JDK 8 runtime compatibility via multi-release JAR structure
49a58a7
to
b78c9a0
Compare
- Add package-info.java for com.google.common.truth.extensions.proto - Add package-info.java for com.google.common.truth.extensions.liteproto - Clarify differences between full protobuf and protobuf-lite extensions - Maintain proper Javadoc coverage for exported packages These were inadvertently removed during JPMS package separation.
Okay, I think I fixed all the problems. Please review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I suggest once or twice below, please do feel free to scope this down if that is sufficient to accomplish your goals.
*/ | ||
|
||
package com.google.common.truth.extensions.proto; | ||
package com.google.common.truth.extensions.liteproto; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should have thought of this, too. It serves me right for saying how "unlikely" it was that we'd need to release a Truth 2.0.0... :)
Changing the package is a tough sell. We might still be able to pull it off, but it would take some effort for both us and our users, including an internal migration that would probably be mostly straightforward but would take some time.
I'm wondering about a potential alternative: What if we were to put both ProtoTruth and LiteProtoTruth into a single artifact with a dependency on protobuf-java
that is optional
?
Ideally we'd call that artifact truth-proto-extension
, but we might "need to" call it truth-liteproto-extension
instead: That would ensure that users of both of the existing artifacts get the combined artifact automatically—directly for users of truth-liteproto-extension
, transitively for users of truth-proto-extension
(which we'd continue to release but which would become an empty jar that still depends on truth-liteproto-extension
). (If a build tool forbids use of transitive dependencies, then users of that tool will have to explicitly move from truth-proto-extension
to truth-liteproto-extension
.)
This would be roughly the approach that we already used when merging the Java 8 extensions into the core of Truth. The difference would be that, in our previous merge, we could reasonably expect for users of the Java 8 extension to also already declare a dependency on core Truth (or, failing that, to be using a build tool that was happy to let them rely on transitive dependencies). Here, we wouldn't expect a project that uses ProtoTruth to also declare a dependency on LiteProtoTruth. Again, though, that won't be a problem unless the user uses a build tool that is strict about transitive deps.
(Could we have Maven redirect things automatically? I think I saw that done for GWT, but maybe that can be done only for a change to groupId
?)
The other option, of course, is to give up on modularizing the protobuf extensions. I don't know if you're modularizing them because you have a need for that or just because you want to be an even better citizen as you work to modularize the core.
Let me know if you have thoughts on the approach. I can think about it more before encouraging you to redo things yet again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, the fact that we're not able to make any traction at modularizing the protobuf library is a real pain.
Putting aside the protobuf library for a minute, would this approach work for the truth library? protocolbuffers/protobuf#16133 (comment)
Or do you have a similar hierarchy problem as they do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, of course, we can't modularize "for real" until Protobuf does :(
I would really like to avoid duplicating all the existing classes, even those just in the protobuf extensions. Truth (especially ProtoTruth) doesn't get extended the way that Protobuf does, but it would still mean two parallel mini-ecosystems. I'd like to think that we can work out the merging of the two protobuf artifacts if it comes to that.
module com.google.truth { | ||
requires com.google.common; | ||
requires junit; | ||
requires java.compiler; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that this is for @Generated
from AutoValue:
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/core/target/generated-sources/annotations/com/google/common/truth/AutoValue_ActualValueInference_OpaqueEntry.java:[4,24] package javax.annotation.processing is not visible
[ERROR] (package javax.annotation.processing is declared in module java.compiler, but module com.google.truth does not read it)
$ sed -n '4 p' core/target/generated-sources/annotations/com/google/common/truth/AutoValue_ActualValueInference_OpaqueEntry.java
import javax.annotation.processing.Generated;
If I were more ambitious, I would think about this more and possibly open an issue against AutoValue or AutoCommon or something.
It might be that we could avoid this by not performing a "real" Java 9 build, instead only using Java 9 for the module-info
build, as I think you had things initially. A downside to that is that we could more easily omit lines from the module-info
that we ought to have included (as ably demonstrated by Guava in google/guava#7744 and google/guava#7748). (And any change gives us a chance to get anything wrong.)
I think I'm tentatively in favor of leaving this line here but tweaking it to requires static
(here and in whatever other artifacts you are up for pushing through):
requires java.compiler; | |
requires static java.compiler; |
<configuration> | ||
<archive> | ||
<manifestEntries> | ||
<Multi-Release>true</Multi-Release> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the current configuration, I don't think we actually end up producing a multi-release jar, so I think we can get by without this.
<groupId>com.google.truth</groupId> | ||
<artifactId>truth-parent</artifactId> | ||
<version>HEAD-SNAPSHOT</version> | ||
<version>999.0.0-SNAPSHOT</version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error #1 from mvn clean install
:
[INFO] Running com.google.common.truth.extension.EmployeeSubjectTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0 s <<< FAILURE! -- in com.google.common.truth.extension.EmployeeSubjectTest
[ERROR] com.google.common.truth.extension.EmployeeSubjectTest.initializationError -- Time elapsed: 0 s <<< ERROR!
java.lang.reflect.InaccessibleObjectException: Unable to make public void com.google.common.truth.extension.EmployeeSubjectTest.username() accessible: module com.google.truth does not "exports com.google.common.truth.extension" to module junit
at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:353)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:329)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:277)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:182)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:176)
at [email protected]/org.junit.runners.model.FrameworkMethod.<init>(FrameworkMethod.java:35)
at [email protected]/org.junit.runners.model.TestClass.scanAnnotatedMembers(TestClass.java:66)
at [email protected]/org.junit.runners.model.TestClass.<init>(TestClass.java:57)
at [email protected]/org.junit.runners.JUnit4.<init>(JUnit4.java:23)
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:483)
at [email protected]/org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
at [email protected]/org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
at [email protected]/org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at [email protected]/org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:37)
at [email protected]/org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at [email protected]/org.junit.internal.requests.ClassRequest.createRunner(ClassRequest.java:28)
at [email protected]/org.junit.internal.requests.MemoizingRequest.getRunner(MemoizingRequest.java:19)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:314)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:240)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:214)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:155)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)
[INFO] Running com.google.common.truth.extension.FakeHrDatabaseTest
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0 s <<< FAILURE! -- in com.google.common.truth.extension.FakeHrDatabaseTest
[ERROR] com.google.common.truth.extension.FakeHrDatabaseTest.initializationError -- Time elapsed: 0 s <<< ERROR!
java.lang.reflect.InaccessibleObjectException: Unable to make public void com.google.common.truth.extension.FakeHrDatabaseTest.relocatePresent() accessible: module com.google.truth does not "exports com.google.common.truth.extension" to module junit
at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:353)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:329)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:277)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:182)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:176)
at [email protected]/org.junit.runners.model.FrameworkMethod.<init>(FrameworkMethod.java:35)
at [email protected]/org.junit.runners.model.TestClass.scanAnnotatedMembers(TestClass.java:66)
at [email protected]/org.junit.runners.model.TestClass.<init>(TestClass.java:57)
at [email protected]/org.junit.runners.JUnit4.<init>(JUnit4.java:23)
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:483)
at [email protected]/org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:104)
at [email protected]/org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:86)
at [email protected]/org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at [email protected]/org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:37)
at [email protected]/org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at [email protected]/org.junit.internal.requests.ClassRequest.createRunner(ClassRequest.java:28)
at [email protected]/org.junit.internal.requests.MemoizingRequest.getRunner(MemoizingRequest.java:19)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:314)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:240)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:214)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:155)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)
That's kind of just a weird case: We probably "should" have a separate pom.xml
and a separate module for the example extension. Then it could export this extension
package.
I am 100% on board with just removing the sample extension from the testing: It's just a sample, and we'll continue to build and test it internally, so it will remain up to date.
<Multi-Release>true</Multi-Release> | ||
</manifestEntries> | ||
</archive> | ||
</configuration> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error #2 from mvn clean install
:
[INFO] Running com.google.common.truth.CorrespondenceExceptionStoreTest
[ERROR] Tests run: 6, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.009 s <<< FAILURE! -- in com.google.common.truth.CorrespondenceExceptionStoreTest
[ERROR] com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsMainCause_notEmpty -- Time elapsed: 0.007 s <<< FAILURE!
value of:
getValue()
expected to match:
compare\(null, 123\) threw com.google.common.truth.TestCorrespondences\$NullPointerExceptionFromWithin10Of\s+at com\.google\.common\.truth\.TestCorrespondences(.|\n)*\n---
but was:
compare(null, 123) threw com.google.common.truth.TestCorrespondences$NullPointerExceptionFromWithin10Of
at [email protected]/com.google.common.truth.TestCorrespondences.lambda$static$1(TestCorrespondences.java:76)
at [email protected]/com.google.common.truth.Correspondence$FromBinaryPredicate.compare(Correspondence.java:150)
at [email protected]/com.google.common.truth.Correspondence$FormattingDiffs.compare(Correspondence.java:453)
---
at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.assertExpectedFacts(CorrespondenceExceptionStoreTest.java:99)
at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsMainCause_notEmpty(CorrespondenceExceptionStoreTest.java:58)
[ERROR] com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsAdditionalInfo_notEmpty -- Time elapsed: 0.002 s <<< FAILURE!
value of:
getValue()
expected to match:
compare\(null, 123\) threw com.google.common.truth.TestCorrespondences\$NullPointerExceptionFromWithin10Of\s+at com\.google\.common\.truth\.TestCorrespondences(.|\n)*\n---
but was:
compare(null, 123) threw com.google.common.truth.TestCorrespondences$NullPointerExceptionFromWithin10Of
at [email protected]/com.google.common.truth.TestCorrespondences.lambda$static$1(TestCorrespondences.java:76)
at [email protected]/com.google.common.truth.Correspondence$FromBinaryPredicate.compare(Correspondence.java:150)
at [email protected]/com.google.common.truth.Correspondence$FormattingDiffs.compare(Correspondence.java:453)
---
at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.assertExpectedFacts(CorrespondenceExceptionStoreTest.java:99)
at [email protected]/com.google.common.truth.CorrespondenceExceptionStoreTest.describeAsAdditionalInfo_notEmpty(CorrespondenceExceptionStoreTest.java:73)
That should just be a matter of loosening the assertion to accept either style.
Of course, another option for this (and for Error #1) is to see if we can keep maven-surefire-plugin
out of "modules mode" for its testing. Having the modular mode set up is nice if it's straightforward, but I don't want you to feel like you need to block the user-facing improvements you need on an internal build improvement, albeit one that might someday help us catch modules-related bugs before you encounter them.
</manifestEntries> | ||
</archive> | ||
</configuration> | ||
</plugin> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error from mvn clean install -DskipTests
:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.14.0:testCompile (default-testCompile) on project truth-liteproto-extension: Compilation failure: Compilation failure:
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[24,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[25,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[26,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[27,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
[ERROR] /usr/local/google/home/cpovirk/clients/truth-green/truth/extensions/liteproto/src/test/java/com/google/common/truth/extensions/liteproto/test/LiteProtoSubjectTest.java:[28,63] package com.google.common.truth.extensions.liteproto.test.proto does not exist
I haven't thought about that one, but it might be a matter of moving more stuff or updating more references. I'd encourage you not to worry about it until we discuss the proto/liteproto business more, but I'm noting it here as a record of potential remaining work.
Make the JUnit 4 dependency optional Co-authored-by: Chris Povirk <[email protected]>
Sorry, work sucked all the energy out of me this week. I'd love for someone else to pick up where I left off; otherwise, I'll try getting back to this in a couple of weeks. |
Summary
This PR adds Java Platform Module System (JPMS) support to Truth while maintaining full backward compatibility with Java 8.
Changes
module-info.java
with exports forcom.google.common.truth
com.google.truth.extensions.proto
com.google.truth.extensions.liteproto
com.google.truth.extensions.re2j
Benefits
Implementation Details
module-info.java
insrc/main/java9/
Test plan
META-INF/versions/9/module-info.class
)--describe-module
verification)