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