From 385dadafb0ab9835e42c9252f446f3ac607521d2 Mon Sep 17 00:00:00 2001 From: BenCodez <17074231+BenCodez@users.noreply.github.com> Date: Sat, 12 Sep 2026 17:08:39 -0600 Subject: [PATCH] Add thin artifact for downstream shading --- SimpleAPI/pom.xml | 19 +++++++----- .../simpleapi/tests/BungeeJsonFileTest.java | 6 ++-- .../tests/packaging/FullArtifactTest.java | 29 +++++++++++++++++-- docs/jar-packaging.md | 22 +++++++++----- 4 files changed, 56 insertions(+), 20 deletions(-) diff --git a/SimpleAPI/pom.xml b/SimpleAPI/pom.xml index cf7c07e4..bc97dea7 100644 --- a/SimpleAPI/pom.xml +++ b/SimpleAPI/pom.xml @@ -97,6 +97,14 @@ + + thin-library + package + jar + + thin + + shared-library-sources package @@ -149,14 +157,8 @@ META-INF/*.SF META-INF/*.DSA META-INF/*.RSA - - - - - org.bouncycastle:* - + META-INF/versions/** @@ -206,6 +208,7 @@ ${project.build.directory}/full-artifact-reports ${project.build.directory}/${project.build.finalName}.jar + ${project.build.directory}/${project.build.finalName}-thin.jar diff --git a/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/BungeeJsonFileTest.java b/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/BungeeJsonFileTest.java index 7282dbe7..d85480c4 100644 --- a/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/BungeeJsonFileTest.java +++ b/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/BungeeJsonFileTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -13,6 +14,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.io.TempDir; import com.bencodez.simpleapi.file.BungeeJsonFile; import com.google.gson.JsonObject; @@ -24,9 +26,9 @@ public class BungeeJsonFileTest { private BungeeJsonFile bungeeJsonFile; @BeforeAll - public void setup() { + public void setup(@TempDir Path temporary) { // Create a temporary file for testing - testFile = new File("test.json"); + testFile = temporary.resolve("test.json").toFile(); bungeeJsonFile = new BungeeJsonFile(testFile); } diff --git a/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/packaging/FullArtifactTest.java b/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/packaging/FullArtifactTest.java index 845dd4c0..a73df7cd 100644 --- a/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/packaging/FullArtifactTest.java +++ b/SimpleAPI/src/test/java/com/bencodez/simpleapi/tests/packaging/FullArtifactTest.java @@ -23,7 +23,7 @@ public class FullArtifactTest { @TempDir Path temporary; - @Test void retainsBaseCryptoClassesWithoutUnusedVersionedPayload() throws Exception { + @Test void retainsBaseCryptoClassesWithoutUnusableVersionedPayload() throws Exception { Path full = fullJar(); long removedEntries = 0; long removedCompressedBytes = 0; @@ -35,8 +35,8 @@ public class FullArtifactTest { "Revisit the BC filter before making the full artifact multi-release"); String classPath = output.getManifest().getMainAttributes().getValue(Attributes.Name.CLASS_PATH); assertTrue(classPath == null || classPath.isBlank(), "Smoke test must not load external manifest dependencies"); - assertFalse(output.stream().anyMatch(entry -> entry.getName().startsWith("META-INF/versions/") - && entry.getName().contains("/org/bouncycastle/")), "Unused versioned BC payload is still bundled"); + assertFalse(output.stream().anyMatch(entry -> entry.getName().startsWith("META-INF/versions/")), + "A non-multi-release artifact must not bundle unreachable versioned implementations"); // Resolve all three original libraries from Maven, without a pinned version or ~/.m2 path. for (Class anchor : List.of(BouncyCastleProvider.class, X509CertificateHolder.class, ContentInfo.class)) { @@ -62,6 +62,21 @@ public class FullArtifactTest { Files.size(full), retainedEntries, removedEntries, removedCompressedBytes); } + @Test void thinArtifactContainsProjectClassesWithoutEmbeddedDependencies() throws Exception { + Path thin = thinJar(); + try (JarFile artifact = new JarFile(thin.toFile())) { + assertNotNull(artifact.getEntry("com/bencodez/simpleapi/servercomm/http/HttpTlsIdentity.class"), + "Thin artifact must preserve the complete SimpleAPI API"); + assertNotNull(artifact.getEntry("com/bencodez/simpleapi/scheduler/BukkitScheduler.class")); + assertNull(artifact.getEntry("org/bouncycastle/jce/provider/BouncyCastleProvider.class")); + assertNull(artifact.getEntry("com/zaxxer/hikari/HikariDataSource.class")); + assertNull(artifact.getEntry("redis/clients/jedis/Jedis.class")); + assertNull(artifact.getEntry("org/spongepowered/configurate/ConfigurationNode.class")); + assertFalse(artifact.stream().anyMatch(entry -> entry.getName().startsWith("META-INF/versions/"))); + } + System.out.printf("Thin artifact: %,d bytes (project classes only)%n", Files.size(thin)); + } + @Test void packagedTlsWorksWithoutMavenDependencies() throws Exception { Path full = fullJar(); String fixtureName = PackagedTlsSmoke.class.getName(); @@ -104,4 +119,12 @@ private static Path fullJar() { assertTrue(Files.isRegularFile(full), "Missing packaged full artifact: " + full); return full; } + + private static Path thinJar() { + String value = System.getProperty("simpleapi.thinJar"); + assertNotNull(value, "Run this test through the Maven package lifecycle"); + Path thin = Path.of(value).toAbsolutePath().normalize(); + assertTrue(Files.isRegularFile(thin), "Missing packaged thin artifact: " + thin); + return thin; + } } diff --git a/docs/jar-packaging.md b/docs/jar-packaging.md index 2cf1c4e6..1eac2a27 100644 --- a/docs/jar-packaging.md +++ b/docs/jar-packaging.md @@ -4,16 +4,23 @@ `shared-sources` classifiers, dependency scopes, and public APIs are unchanged. There are no additional modules or runtime downloads. +The `thin` classifier contains the complete, unshaded SimpleAPI classes and +resources but no embedded third-party classes. It exists for trusted downstream +projects that immediately shade SimpleAPI while explicitly controlling the +ordinary POM's transitive dependencies. It is not a standalone replacement for +the full artifact. In particular, a consumer of HTTP/TLS APIs must still supply +the declared Bouncy Castle libraries. Normal external consumers should continue +to use the self-contained main artifact. + The HTTP transport uses Bouncy Castle for its private CA and certificates. Do not remove those dependencies, switch them to `provided`, strip provider mappings, or enable broad `minimizeJar` without packaged-runtime validation. Providers load some implementation classes reflectively. -The full shaded artifact is not a multi-release JAR. Its BC-specific shade -filter omits `META-INF/versions/**`, which that artifact cannot select, while -retaining all base BC classes and resources. Other dependencies are not filtered -by this rule. This is a conservative reduction of redundant payload, not removal -of the crypto provider or the entire HTTP dependency cost. +The full shaded artifact is not a multi-release JAR. Its shade filter omits +`META-INF/versions/**`, which that artifact cannot select, while retaining base +classes and resources. This is a conservative reduction of unreachable payload, +not removal of the crypto provider or the entire HTTP dependency cost. Java only selects versioned classes when the final manifest declares `Multi-Release: true`. If that contract changes, revisit this filter and the @@ -31,8 +38,9 @@ git diff --check The package phase runs `FullArtifactTest` after shading, followed by the existing shared-classpath tests. It verifies the non-multi-release manifest, absence of -versioned BC payload, and preservation of every base `org/bouncycastle/` entry -from the three resolved BC libraries. It prints the final JAR size and the +all unreachable versioned payload, preservation of every base +`org/bouncycastle/` entry from the three resolved BC libraries, and the thin +artifact's no-embedded-dependencies contract. It prints the final JAR size and the compressed upstream payload omitted. That payload counter is not an exact before/after distribution size: shading/recompression and ZIP overhead differ. To measure the exact reduction, compare clean baseline and candidate builds with