Repository navigation
Description
What happens
protobuf-setup-plugins applies the uber-jar-module plugin, so io.spine.tools:protobuf-setup-plugins is published as a Shadow JAR, and its classes are not relocated. unbundleProtobufRuntime() keeps the Protobuf runtime out of the JAR, and the POM declares only that runtime. Everything else that the module uses is inside the JAR. In 2.0.0-SNAPSHOT.430, which is 5.4 MB with 3,396 classes, that is:
- Other ToolBase modules:
plugin-base(io.spine.tools.gradle, includinggradle.project.Projectsandgradle.task),code,fs,archive,java-code,proto-code, andjvm-tools(io.spine.tools.meta,jvm,io,version). - Spine libraries: Base and Logging.
- Third-party libraries: Guava
33.7.2-jre, Gson2.14.0, and the Protobuf Gradle plugin0.10.0with osdetector, including their plugin descriptorsMETA-INF/gradle-plugins/com.google.protobuf.propertiesandcom.google.osdetector.properties. Classes undercom/googlemake up 2,351 of the total.
The Gradle module metadata publishes a single shadowRuntimeElements variant (org.gradle.dependency.bundling = shadowed). The protobuf-setup-plugins-all artifact of the fatJar publication is byte-identical.
The Spine Compiler Gradle plugin depends on this artifact. The POM of compiler-gradle-plugin 2.0.0-SNAPSHOT.082 declares protobuf-setup-plugins .430 next to plugin-base, code, fs, and jvm-tools. So every build that applies io.spine.compiler gets the following on one build-script classloader:
- the ToolBase artifacts, at the versions that Gradle resolves for the build;
- copies of the same classes inside
protobuf-setup-plugins, at the version thatprotobuf-setup-pluginswas built with; - copies of Guava, Gson, and the Protobuf Gradle plugin, next to the versions that the build resolves for itself.
Gradle resolves each module to a single version, but it cannot know that protobuf-setup-plugins carries other copies of the same classes. The JVM loads whichever JAR comes first on the classpath.
Evidence
- The embedded copies follow
protobuf-setup-plugins, notplugin-base.javaponio.spine.tools.gradle.project.Projectsshows that the copy inprotobuf-setup-plugins-2.0.0-SNAPSHOT.428.jarlacksjavaCompileTasksFor()andkotlinCompileTasksFor(), and the copy in.430has them. They were added in Find tasks lazily inplugin-baseandprotobuf-setup-plugins#199. - The versions can diverge. ToolBase has no BOM, so Gradle resolves each ToolBase module on its own. When another plugin on the classpath requires
plugin-base.430and the Compiler Gradle plugin still requiresprotobuf-setup-plugins.428, Gradle upgradesplugin-basebut keepsprotobuf-setup-pluginsat.428. WhichProjectsloads then depends on the classpath order.
Why it matters
- The same failure as Stop bundling ToolBase classes in the
core-jvm-pluginsfat JAR core-jvm-compiler#124. There, an unrelocated copy ofProjectsin thecore-jvm-pluginsfat JAR shadowedplugin-base.430, and the build failed with aNoSuchMethodErroronProjects.javaCompileTasksFor(). CoreJvm Compiler is removing its copy, butprotobuf-setup-pluginskeeps another one on the same classpath. - Bumping a ToolBase module is not enough. A plugin that needs new API or a fix from
plugin-base,code,fs, orjvm-toolsgets it only ifprotobuf-setup-pluginsis bumped too, or happens to come later on the classpath. - Consumers do not control the third-party versions. Constraints and
resolutionStrategydo not reach classes inside a JAR. A build that declares another version of Guava, Gson, or the Protobuf Gradle plugin may still run the classes embedded here. - Mismatches are silent. The build fails only when a member that is missing from the embedded copy is called. A change in behavior goes unnoticed.
Suggested fix
Publish protobuf-setup-plugins as a plain JAR with ordinary dependencies:
- Apply
moduleinstead ofuber-jar-module, asplugin-baseandjvm-toolsdo. - Let the POM and the Gradle module metadata declare the module's dependencies: the ToolBase modules, Base, Logging, and the Protobuf Gradle plugin.
Relocating the embedded classes instead would break the public API. Functions such as Project.descriptorSetFile(SourceSetName) and GenerateProtoTask.sourceSetName take or extend code and Protobuf Gradle plugin types. Relocated copies of those types would not match the ones that callers pass in.
To check while doing it:
- The
-allpublication.protobuf-setup-plugins-allis the same JAR. It can go unless something consumes it. - The Gradle Plugin Portal. Release versions are also published there. The declared dependencies must resolve for Portal users.
jvm-tool-plugins. It also appliesuber-jar-module, and it may have the same problem.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fields✅ Done
Activity