After #168 set duplicatesStrategy = DuplicatesStrategy.INCLUDE so the
shadow plugin’s mergeServiceFiles() transformer could see duplicate
service files, ./gradlew :server:proguard started failing with:
1
2
3
java.io.IOException: Can't write [/home/ben/Code/krill/server/build/libs/server-min.jar]
(Can't read [/home/ben/Code/krill/server/build/libs/server-all.jar]
(Duplicate jar entry [io/netty/handler/codec/AsciiHeadersEncoder$1.class]))
server-all.jar was readable by Java’s stricter consumers but ProGuard’s
zip reader rejected the duplicate entries — the deb couldn’t be obfuscated
or shipped.
INCLUDE keeps every duplicate of every path, not just service files. AWS
SDK pins io.netty:netty-codec:4.1.x while ktor and other deps pull
io.netty:netty-codec-base:4.2.x; netty’s 4.1 → 4.2 module split renamed
netty-codec to netty-codec-base but the two artifacts still ship a
non-trivial overlap of class FQCNs (io/netty/handler/codec/AsciiHeadersEncoder$1.class
and many more). With INCLUDE, both copies of every shared class survive
into the fat jar — legal-ish ZIP, but ProGuard chokes. EXCLUDE was the
only setting that produced a clean jar, but it dropped the gRPC service
files #168 was protecting.
Keep duplicatesStrategy = DuplicatesStrategy.EXCLUDE (so the jar stays
ProGuard-readable), drop mergeServiceFiles(), and re-merge the gRPC
service files manually in the existing doLast after shadowJar finishes.
The merge reads every META-INF/services/io.grpc.* entry from each jar
in jvmRuntimeClasspath, dedupes the lines into a LinkedHashSet, and
rewrites the output via a jar: FileSystem mount. The post-build
class-presence assertion (#164) and service-file-content assertion (#168)
both run after the merge, so a future regression in either packaging
axis still fails CI.
Verified: :server:shadowJar produces server-all.jar with both gRPC
NameResolverProviders listed; :server:proguard succeeds and
server-min.jar retains the merged service files.
Three packaging regressions on the same gRPC bootstrap path now have
build-time guards: classes pruned by minimize (#164), service files
dropped by duplicatesStrategy (#168), and ProGuard rejecting INCLUDE’s
duplicates (#171). The class-FQCN overlap between netty 4.1 and 4.2
artifacts is the underlying root cause that ruled out the documented
shadow mergeServiceFiles() approach — if a future dep upgrade resolves
the netty conflict to a single major version, switching back to
mergeServiceFiles() + INCLUDE would be cleaner. Until then, the manual
merge is the only safe option.